From b88b7e4e519881ffec0d09590347683cc8934632 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 24 Sep 2018 11:40:22 -0400 Subject: [PATCH 01/22] Reflect breaking v2 changes in examples and tests --- README.md | 19 +++++-------------- post_test.go | 9 ++------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d1ee8d3..47a633f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # go-writeas -[![godoc](https://godoc.org/go.code.as/writeas.v1?status.svg)](https://godoc.org/go.code.as/writeas.v1) +[![godoc](https://godoc.org/go.code.as/writeas.v2?status.svg)](https://godoc.org/go.code.as/writeas.v2) Official Write.as Go client library. ## Installation ```bash -go get go.code.as/writeas.v1 +go get go.code.as/writeas.v2 ``` ## Documentation @@ -17,7 +17,7 @@ See all functionality and usages in the [API documentation](https://developer.wr ### Example usage ```go -import "go.code.as/writeas.v1" +import "go.code.as/writeas.v2" func main() { // Create the client @@ -37,11 +37,7 @@ func main() { token := p.Token // Update a published post - p, err = c.UpdatePost(&writeas.PostParams{ - OwnedPostParams: writeas.OwnedPostParams{ - ID: p.ID, - Token: token, - }, + p, err = c.UpdatePost(p.ID, token, &writeas.PostParams{ Content: "Now it's been updated!", }) if err != nil { @@ -55,12 +51,7 @@ func main() { } // Delete a post - err = c.DeletePost(&writeas.PostParams{ - OwnedPostParams: writeas.OwnedPostParams{ - ID: p.ID, - Token: token, - }, - }) + err = c.DeletePost(p.ID, token) } ``` diff --git a/post_test.go b/post_test.go index abdfa3a..f995f2b 100644 --- a/post_test.go +++ b/post_test.go @@ -23,9 +23,7 @@ func TestCreatePost(t *testing.T) { token := p.Token // Update post - p, err = wac.UpdatePost(&PostParams{ - ID: p.ID, - Token: token, + p, err = wac.UpdatePost(p.ID, token, &PostParams{ Content: "Now it's been updated!", }) if err != nil { @@ -35,10 +33,7 @@ func TestCreatePost(t *testing.T) { t.Logf("Post updated: %+v", p) // Delete post - err = wac.DeletePost(&PostParams{ - ID: p.ID, - Token: token, - }) + err = wac.DeletePost(p.ID, token) if err != nil { t.Errorf("Post delete failed: %v", err) return From cffca70254b403141633e832156ed161c7be8a83 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 24 Sep 2018 11:41:04 -0400 Subject: [PATCH 02/22] Add v2 warning in README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 47a633f..17cc920 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Official Write.as Go client library. ## Installation +**Warning**: the `v2` branch is under heavy development and its API will change without notice. + +For a stable API, use `go.code.as/writeas.v1` and upgrade to `v2` once everything is merged into `master`. + ```bash go get go.code.as/writeas.v2 ``` From ca804e86e2d9012c56b44c9b5506d3c51a94d1b2 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 25 Sep 2018 12:11:25 -0400 Subject: [PATCH 03/22] Fix "problem {x}ing post" copy-pasted error messages Previously, they all said the same thing --- post.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/post.go b/post.go index 977918e..ac10fcc 100644 --- a/post.go +++ b/post.go @@ -129,7 +129,7 @@ func (c *Client) CreatePost(sp *PostParams) (*Post, error) { } else if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } else { - return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem creating post: %d. %v\n", status, err) } } @@ -154,7 +154,7 @@ func (c *Client) UpdatePost(sp *PostParams) (*Post, error) { } else if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } - return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem updating post: %d. %v\n", status, err) } return p, nil } @@ -177,7 +177,7 @@ func (c *Client) DeletePost(sp *PostParams) error { } else if status == http.StatusBadRequest { return fmt.Errorf("Bad request: %s", env.ErrorMessage) } - return fmt.Errorf("Problem getting post: %d. %v\n", status, err) + return fmt.Errorf("Problem deleting post: %d. %v\n", status, err) } // ClaimPosts associates anonymous posts with a user / account. @@ -202,7 +202,7 @@ func (c *Client) ClaimPosts(sp *[]OwnedPostParams) (*[]ClaimPostResult, error) { } else if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } else { - return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem claiming post: %d. %v\n", status, err) } // TODO: does this also happen with moving posts? } @@ -226,7 +226,7 @@ func (c *Client) GetUserPosts() (*[]Post, error) { if c.isNotLoggedIn(status) { return nil, fmt.Errorf("Not authenticated.") } - return nil, fmt.Errorf("Problem getting posts: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem getting user posts: %d. %v\n", status, err) } return p, nil } From 5d3a329a7a50f013a204f4d8134f62032c5ba6bd Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 25 Sep 2018 12:24:51 -0400 Subject: [PATCH 04/22] Rearrange status checks in CreatePost --- post.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/post.go b/post.go index ac10fcc..4abda8e 100644 --- a/post.go +++ b/post.go @@ -124,13 +124,13 @@ func (c *Client) CreatePost(sp *PostParams) (*Post, error) { } status := env.Code - if status == http.StatusCreated { - return p, nil - } else if status == http.StatusBadRequest { - return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) - } else { + if status != http.StatusCreated { + if status == http.StatusBadRequest { + return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) + } return nil, fmt.Errorf("Problem creating post: %d. %v\n", status, err) } + return p, nil } // UpdatePost updates a published post with the given PostParams. See From 25642072974bc97a09dbd4cd96e06ad666f057cf Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 25 Sep 2018 12:28:25 -0400 Subject: [PATCH 05/22] Return actual error message when request fails --- post.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/post.go b/post.go index 4abda8e..6c3be58 100644 --- a/post.go +++ b/post.go @@ -102,7 +102,7 @@ func (c *Client) GetPost(id string) (*Post, error) { } else if status == http.StatusGone { return nil, fmt.Errorf("Post unpublished.") } - return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem getting post: %d. %s\n", status, env.ErrorMessage) } // CreatePost publishes a new post, returning a user-friendly error if one comes @@ -128,7 +128,7 @@ func (c *Client) CreatePost(sp *PostParams) (*Post, error) { if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } - return nil, fmt.Errorf("Problem creating post: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem creating post: %d. %s\n", status, env.ErrorMessage) } return p, nil } @@ -154,7 +154,7 @@ func (c *Client) UpdatePost(sp *PostParams) (*Post, error) { } else if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } - return nil, fmt.Errorf("Problem updating post: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem updating post: %d. %s\n", status, env.ErrorMessage) } return p, nil } @@ -177,7 +177,7 @@ func (c *Client) DeletePost(sp *PostParams) error { } else if status == http.StatusBadRequest { return fmt.Errorf("Bad request: %s", env.ErrorMessage) } - return fmt.Errorf("Problem deleting post: %d. %v\n", status, err) + return fmt.Errorf("Problem deleting post: %d. %s\n", status, env.ErrorMessage) } // ClaimPosts associates anonymous posts with a user / account. @@ -202,7 +202,7 @@ func (c *Client) ClaimPosts(sp *[]OwnedPostParams) (*[]ClaimPostResult, error) { } else if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } else { - return nil, fmt.Errorf("Problem claiming post: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem claiming post: %d. %s\n", status, env.ErrorMessage) } // TODO: does this also happen with moving posts? } @@ -226,7 +226,7 @@ func (c *Client) GetUserPosts() (*[]Post, error) { if c.isNotLoggedIn(status) { return nil, fmt.Errorf("Not authenticated.") } - return nil, fmt.Errorf("Problem getting user posts: %d. %v\n", status, err) + return nil, fmt.Errorf("Problem getting user posts: %d. %s\n", status, env.ErrorMessage) } return p, nil } @@ -251,7 +251,7 @@ func (c *Client) PinPost(alias string, pp *PinnedPostParams) error { if c.isNotLoggedIn(status) { return fmt.Errorf("Not authenticated.") } - return fmt.Errorf("Problem pinning post: %d. %v\n", status, err) + return fmt.Errorf("Problem pinning post: %d. %s\n", status, env.ErrorMessage) } // Check the individual post result @@ -261,7 +261,7 @@ func (c *Client) PinPost(alias string, pp *PinnedPostParams) error { if (*res)[0].Code != http.StatusOK { return fmt.Errorf("Problem pinning post: %d", (*res)[0].Code) // TODO: return ErrorMessage (right now it'll be empty) - // return fmt.Errorf("Problem pinning post: %v", res[0].ErrorMessage) + // return fmt.Errorf("Problem pinning post: %s", res[0].ErrorMessage) } return nil } @@ -286,7 +286,7 @@ func (c *Client) UnpinPost(alias string, pp *PinnedPostParams) error { if c.isNotLoggedIn(status) { return fmt.Errorf("Not authenticated.") } - return fmt.Errorf("Problem unpinning post: %d. %v\n", status, err) + return fmt.Errorf("Problem unpinning post: %d. %s\n", status, env.ErrorMessage) } // Check the individual post result @@ -296,7 +296,7 @@ func (c *Client) UnpinPost(alias string, pp *PinnedPostParams) error { if (*res)[0].Code != http.StatusOK { return fmt.Errorf("Problem unpinning post: %d", (*res)[0].Code) // TODO: return ErrorMessage (right now it'll be empty) - // return fmt.Errorf("Problem unpinning post: %v", res[0].ErrorMessage) + // return fmt.Errorf("Problem unpinning post: %s", res[0].ErrorMessage) } return nil } From 470dc287c67d3d42488013717d740852bf71d611 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 8 Oct 2018 20:03:22 -0400 Subject: [PATCH 06/22] Move update params in Update|DeletePost Moves the ID and token params out of PostParams and into the func's parameters. A currently-unused `collection` parameter is left in for the future, when the backend supports updating and deleting collection posts via slug, not post ID. --- post.go | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/post.go b/post.go index 6c3be58..b023a43 100644 --- a/post.go +++ b/post.go @@ -135,9 +135,19 @@ func (c *Client) CreatePost(sp *PostParams) (*Post, error) { // UpdatePost updates a published post with the given PostParams. See // https://developer.write.as/docs/api/#update-a-post. -func (c *Client) UpdatePost(sp *PostParams) (*Post, error) { +func (c *Client) UpdatePost(id, token string, sp *PostParams) (*Post, error) { + return c.updatePost("", id, token, sp) +} + +func (c *Client) updatePost(collection, identifier, token string, sp *PostParams) (*Post, error) { p := &Post{} - env, err := c.put(fmt.Sprintf("/posts/%s", sp.ID), sp, p) + endpoint := "/posts/" + identifier + /* + if collection != "" { + endpoint = "/collections/" + collection + endpoint + } + */ + env, err := c.put(endpoint, sp, p) if err != nil { return nil, err } @@ -161,10 +171,22 @@ func (c *Client) UpdatePost(sp *PostParams) (*Post, error) { // DeletePost permanently deletes a published post. See // https://developer.write.as/docs/api/#delete-a-post. -func (c *Client) DeletePost(sp *PostParams) error { - env, err := c.delete(fmt.Sprintf("/posts/%s", sp.ID), map[string]string{ - "token": sp.Token, - }) +func (c *Client) DeletePost(id, token string) error { + return c.deletePost("", id, token) +} + +func (c *Client) deletePost(collection, identifier, token string) error { + p := map[string]string{} + endpoint := "/posts/" + identifier + /* + if collection != "" { + endpoint = "/collections/" + collection + endpoint + } else { + p["token"] = token + } + */ + p["token"] = token + env, err := c.delete(endpoint, p) if err != nil { return err } From 9f0a63ee86d9f977207f58842db3e11874f9b48b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 8 Oct 2018 20:15:30 -0400 Subject: [PATCH 07/22] Add Version constant --- writeas.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/writeas.go b/writeas.go index a31b636..0b4e840 100644 --- a/writeas.go +++ b/writeas.go @@ -16,6 +16,9 @@ const ( apiURL = "https://write.as/api" devAPIURL = "https://development.write.as/api" torAPIURL = "http://writeas7pm7rcdqg.onion/api" + + // Current go-writeas version + Version = "2-dev" ) // Client is used to interact with the Write.as API. It can be used to make @@ -161,7 +164,7 @@ func (c *Client) doRequest(r *http.Request, result interface{}) (*impart.Envelop func (c *Client) prepareRequest(r *http.Request) { ua := c.UserAgent if ua == "" { - ua = "go-writeas v1" + ua = "go-writeas v" + Version } r.Header.Add("User-Agent", ua) r.Header.Add("Content-Type", "application/json") From cbbd4ec209559f6a0863806264e32a3c87fd4004 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 8 Oct 2018 20:21:37 -0400 Subject: [PATCH 08/22] Pass along edit token on post update Fixes bug introduced when we moved `Token` into the func's parameters, out of PostParams. --- post.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/post.go b/post.go index b023a43..f748c6e 100644 --- a/post.go +++ b/post.go @@ -145,8 +145,11 @@ func (c *Client) updatePost(collection, identifier, token string, sp *PostParams /* if collection != "" { endpoint = "/collections/" + collection + endpoint + } else { + sp.Token = token } */ + sp.Token = token env, err := c.put(endpoint, sp, p) if err != nil { return nil, err From 2ad70a7602efad1bb3c3e4cda7e1522364f66b22 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Sun, 16 Dec 2018 13:42:16 -0800 Subject: [PATCH 09/22] Add NewClientWith constructor This adds a new constructor to build Clients which operates on a Config struct rather than positional parameters. This will enable adding new parameters in the future without breaking the API. To begin with, this introduces a new URL parameter that wasn't previously available. An example of another parameter that could be added in the future is the timeout (to override the default timeout). Existing constructors have been transitioned to the new one because it implements a superset of their functionality. Minor note: The Tor constructor wasn't respecting the default timeout but with this version, it does. Resolves #8 --- writeas.go | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/writeas.go b/writeas.go index 0b4e840..f31cfbc 100644 --- a/writeas.go +++ b/writeas.go @@ -44,31 +44,55 @@ const defaultHTTPTimeout = 10 * time.Second // c := writeas.NewClient() // c.SetToken("00000000-0000-0000-0000-000000000000") func NewClient() *Client { - return &Client{ - client: &http.Client{Timeout: defaultHTTPTimeout}, - baseURL: apiURL, - } + return NewClientWith(Config{URL: apiURL}) } // NewTorClient creates a new API client for communicating with the Write.as // Tor hidden service, using the given port to connect to the local SOCKS // proxy. func NewTorClient(port int) *Client { - dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, fmt.Sprintf("127.0.0.1:%d", port)) - transport := &http.Transport{Dial: dialSocksProxy} - return &Client{ - client: &http.Client{Transport: transport}, - baseURL: torAPIURL, - } + return NewClientWith(Config{URL: torAPIURL, TorPort: port}) } // NewDevClient creates a new API client for development and testing. It'll // communicate with our development servers, and SHOULD NOT be used in // production. func NewDevClient() *Client { + return NewClientWith(Config{URL: devAPIURL}) +} + +// Config configures a Write.as client. +type Config struct { + // URL of the Write.as API service. Defaults to https://write.as/api. + URL string + + // If specified, the API client will communicate with the Write.as Tor + // hidden service using the provided port to connect to the local SOCKS + // proxy. + TorPort int + + // If specified, requests will be authenticated using this user token. + // This may be provided after making a few anonymous requests with + // SetToken. + Token string +} + +// NewClientWith builds a new API client with the provided configuration. +func NewClientWith(c Config) *Client { + if c.URL == "" { + c.URL = apiURL + } + + httpClient := &http.Client{Timeout: defaultHTTPTimeout} + if c.TorPort > 0 { + dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, fmt.Sprintf("127.0.0.1:%d", c.TorPort)) + httpClient.Transport = &http.Transport{Dial: dialSocksProxy} + } + return &Client{ - client: &http.Client{Timeout: defaultHTTPTimeout}, - baseURL: devAPIURL, + client: httpClient, + baseURL: c.URL, + token: c.Token, } } From 993ee50b3dfd42bd3f65686246b42b0ab982ff57 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Tue, 25 Dec 2018 09:27:00 -0800 Subject: [PATCH 10/22] style: Run goimports on everything Similar to writeas/writeas-cli#22, this change runs goimports on all files and changes the recommendation in the Contributing section to do the same. --- README.md | 2 +- auth_test.go | 4 +--- post_test.go | 3 +-- user.go | 4 +--- writeas.go | 5 +++-- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 17cc920..5288001 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ func main() { The library covers our usage, but might not be comprehensive of the API. So we always welcome contributions and improvements from the community. Before sending pull requests, make sure you've done the following: -* Run `go fmt` on all updated .go files. +* Run `goimports` on all updated .go files. * Document all exported structs and funcs. ## License diff --git a/auth_test.go b/auth_test.go index 3c78c7e..a9ece6f 100644 --- a/auth_test.go +++ b/auth_test.go @@ -1,8 +1,6 @@ package writeas -import ( - "testing" -) +import "testing" func TestAuthentication(t *testing.T) { dwac := NewDevClient() diff --git a/post_test.go b/post_test.go index f995f2b..a7e3475 100644 --- a/post_test.go +++ b/post_test.go @@ -1,10 +1,9 @@ package writeas import ( - "testing" - "fmt" "strings" + "testing" ) func TestCreatePost(t *testing.T) { diff --git a/user.go b/user.go index e10f3c8..5973d9c 100644 --- a/user.go +++ b/user.go @@ -1,8 +1,6 @@ package writeas -import ( - "time" -) +import "time" type ( // AuthUser represents a just-authenticated user. It contains information diff --git a/writeas.go b/writeas.go index f31cfbc..1a646e7 100644 --- a/writeas.go +++ b/writeas.go @@ -3,13 +3,14 @@ package writeas import ( "bytes" - "code.as/core/socks" "encoding/json" "fmt" - "github.com/writeas/impart" "io" "net/http" "time" + + "code.as/core/socks" + "github.com/writeas/impart" ) const ( From f962e5052b1392d77e8e2b94cca479ec046b3d2c Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Thu, 27 Dec 2018 23:53:03 -0800 Subject: [PATCH 11/22] collection: Add support for deletion This adds support for deleting collections. Resolves #1. --- collection.go | 24 ++++++++++++++++++++++++ collection_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/collection.go b/collection.go index d63e116..8c00a54 100644 --- a/collection.go +++ b/collection.go @@ -135,3 +135,27 @@ func (c *Client) GetUserCollections() (*[]Collection, error) { } return colls, nil } + +// DeleteCollection permanently deletes a collection and makes any posts on it +// anonymous. +// +// See https://developers.write.as/docs/api/#delete-a-collection. +func (c *Client) DeleteCollection(alias string) error { + endpoint := "/collections/" + alias + env, err := c.delete(endpoint, nil /* data */) + if err != nil { + return err + } + + status := env.Code + switch status { + case http.StatusNoContent: + return nil + case http.StatusUnauthorized: + return fmt.Errorf("Not authenticated.") + case http.StatusBadRequest: + return fmt.Errorf("Bad request: %s", env.ErrorMessage) + default: + return fmt.Errorf("Problem deleting collection: %d. %s\n", status, env.ErrorMessage) + } +} diff --git a/collection_test.go b/collection_test.go index da9a7f3..93e82c7 100644 --- a/collection_test.go +++ b/collection_test.go @@ -2,7 +2,9 @@ package writeas import ( "fmt" + "strings" "testing" + "time" ) func TestGetCollection(t *testing.T) { @@ -51,6 +53,44 @@ func TestGetUserCollections(t *testing.T) { } } +func TestCreateAndDeleteCollection(t *testing.T) { + wac := NewDevClient() + _, err := wac.LogIn("demo", "demo") + if err != nil { + t.Fatalf("Unable to log in: %v", err) + } + defer wac.LogOut() + + now := time.Now().Unix() + alias := fmt.Sprintf("test-collection-%v", now) + c, err := wac.CreateCollection(&CollectionParams{ + Alias: alias, + Title: fmt.Sprintf("Test Collection %v", now), + }) + if err != nil { + t.Fatalf("Unable to create collection %q: %v", alias, err) + } + + if err := wac.DeleteCollection(c.Alias); err != nil { + t.Fatalf("Unable to delete collection %q: %v", alias, err) + } +} + +func TestDeleteCollectionUnauthenticated(t *testing.T) { + wac := NewDevClient() + + now := time.Now().Unix() + alias := fmt.Sprintf("test-collection-does-not-exist-%v", now) + err := wac.DeleteCollection(alias) + if err == nil { + t.Fatalf("Should not be able to delete collection %q unauthenticated.", alias) + } + + if !strings.Contains(err.Error(), "Not authenticated") { + t.Fatalf("Error message should be more informative: %v", err) + } +} + func ExampleClient_GetCollection() { c := NewClient() coll, err := c.GetCollection("blog") From 1d34eede72c856b5aa9a31ed4036f42aed3111eb Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Fri, 28 Dec 2018 06:42:42 -0800 Subject: [PATCH 12/22] DeleteCollection: Make params a struct Rather than accepting a naked string, accept a DeleteCollectionParams struct so that new optional parameters can be added in the future without breaking the API. --- collection.go | 10 ++++++++-- collection_test.go | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/collection.go b/collection.go index 8c00a54..9d16e48 100644 --- a/collection.go +++ b/collection.go @@ -29,6 +29,12 @@ type ( Alias string `json:"alias"` Title string `json:"title"` } + + // DeleteCollectionParams holds the parameters required to delete a + // collection. + DeleteCollectionParams struct { + Alias string `json:"-"` + } ) // CreateCollection creates a new collection, returning a user-friendly error @@ -140,8 +146,8 @@ func (c *Client) GetUserCollections() (*[]Collection, error) { // anonymous. // // See https://developers.write.as/docs/api/#delete-a-collection. -func (c *Client) DeleteCollection(alias string) error { - endpoint := "/collections/" + alias +func (c *Client) DeleteCollection(p *DeleteCollectionParams) error { + endpoint := "/collections/" + p.Alias env, err := c.delete(endpoint, nil /* data */) if err != nil { return err diff --git a/collection_test.go b/collection_test.go index 93e82c7..905290e 100644 --- a/collection_test.go +++ b/collection_test.go @@ -71,7 +71,8 @@ func TestCreateAndDeleteCollection(t *testing.T) { t.Fatalf("Unable to create collection %q: %v", alias, err) } - if err := wac.DeleteCollection(c.Alias); err != nil { + p := &DeleteCollectionParams{Alias: c.Alias} + if err := wac.DeleteCollection(p); err != nil { t.Fatalf("Unable to delete collection %q: %v", alias, err) } } @@ -81,7 +82,8 @@ func TestDeleteCollectionUnauthenticated(t *testing.T) { now := time.Now().Unix() alias := fmt.Sprintf("test-collection-does-not-exist-%v", now) - err := wac.DeleteCollection(alias) + p := &DeleteCollectionParams{Alias: alias} + err := wac.DeleteCollection(p) if err == nil { t.Fatalf("Should not be able to delete collection %q unauthenticated.", alias) } From 3fb123eead4a70522d2cfad44efe888f89444a44 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Tue, 18 Dec 2018 11:26:02 -0600 Subject: [PATCH 13/22] Support Go Modules --- go.mod | 8 ++++++++ go.sum | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b88b28a --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/writeas/go-writeas/v2 + +go 1.9 + +require ( + code.as/core/socks v1.0.0 + github.com/writeas/impart v1.1.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3e036d3 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs= +code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY= +github.com/writeas/impart v1.1.0 h1:nPnoO211VscNkp/gnzir5UwCDEvdHThL5uELU60NFSE= +github.com/writeas/impart v1.1.0/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y= From d4fd65f9c06a466538a659668d524fc9bfdb9d81 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 9 Feb 2019 07:10:47 -0500 Subject: [PATCH 14/22] Add Description to CollectionParams for creating a new collection. --- collection.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/collection.go b/collection.go index d63e116..d4c3771 100644 --- a/collection.go +++ b/collection.go @@ -26,8 +26,9 @@ type ( // CollectionParams holds values for creating a collection. CollectionParams struct { - Alias string `json:"alias"` - Title string `json:"title"` + Alias string `json:"alias"` + Title string `json:"title"` + Description string `json:"description,omitempty"` } ) From 062df41a63e7c515a817d5f42e74d99dd1c85bff Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 9 Feb 2019 07:11:45 -0500 Subject: [PATCH 15/22] Add Slug, Created, and Updated to PostParams for creating or updating posts. --- post.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/post.go b/post.go index f748c6e..be6bdca 100644 --- a/post.go +++ b/post.go @@ -42,11 +42,14 @@ type ( Token string `json:"token,omitempty"` // Parameters for creating or updating - Title string `json:"title,omitempty"` - Content string `json:"body,omitempty"` - Font string `json:"font,omitempty"` - IsRTL *bool `json:"rtl,omitempty"` - Language *string `json:"lang,omitempty"` + Slug string `json:"slug"` + Created *time.Time `json:"created,omitempty"` + Updated *time.Time `json:"updated,omitempty"` + Title string `json:"title,omitempty"` + Content string `json:"body,omitempty"` + Font string `json:"font,omitempty"` + IsRTL *bool `json:"rtl,omitempty"` + Language *string `json:"lang,omitempty"` // Parameters only for creating Crosspost []map[string]string `json:"crosspost,omitempty"` From 7a2c93ae6d3ee91dbe7cdd9c68b3d20be43762a2 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 26 May 2019 14:08:05 -0400 Subject: [PATCH 16/22] Revert "DeleteCollection: Make params a struct" This reverts commit 1d34eede72c856b5aa9a31ed4036f42aed3111eb. --- collection.go | 10 ++-------- collection_test.go | 6 ++---- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/collection.go b/collection.go index 9d16e48..8c00a54 100644 --- a/collection.go +++ b/collection.go @@ -29,12 +29,6 @@ type ( Alias string `json:"alias"` Title string `json:"title"` } - - // DeleteCollectionParams holds the parameters required to delete a - // collection. - DeleteCollectionParams struct { - Alias string `json:"-"` - } ) // CreateCollection creates a new collection, returning a user-friendly error @@ -146,8 +140,8 @@ func (c *Client) GetUserCollections() (*[]Collection, error) { // anonymous. // // See https://developers.write.as/docs/api/#delete-a-collection. -func (c *Client) DeleteCollection(p *DeleteCollectionParams) error { - endpoint := "/collections/" + p.Alias +func (c *Client) DeleteCollection(alias string) error { + endpoint := "/collections/" + alias env, err := c.delete(endpoint, nil /* data */) if err != nil { return err diff --git a/collection_test.go b/collection_test.go index 905290e..93e82c7 100644 --- a/collection_test.go +++ b/collection_test.go @@ -71,8 +71,7 @@ func TestCreateAndDeleteCollection(t *testing.T) { t.Fatalf("Unable to create collection %q: %v", alias, err) } - p := &DeleteCollectionParams{Alias: c.Alias} - if err := wac.DeleteCollection(p); err != nil { + if err := wac.DeleteCollection(c.Alias); err != nil { t.Fatalf("Unable to delete collection %q: %v", alias, err) } } @@ -82,8 +81,7 @@ func TestDeleteCollectionUnauthenticated(t *testing.T) { now := time.Now().Unix() alias := fmt.Sprintf("test-collection-does-not-exist-%v", now) - p := &DeleteCollectionParams{Alias: alias} - err := wac.DeleteCollection(p) + err := wac.DeleteCollection(alias) if err == nil { t.Fatalf("Should not be able to delete collection %q unauthenticated.", alias) } From 8728275c31a54f999de597e245ba7a918fb81053 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 26 May 2019 14:39:04 -0400 Subject: [PATCH 17/22] `Set` User-Agent header instead of `Add`ing it --- writeas.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/writeas.go b/writeas.go index 1a646e7..fa87ae1 100644 --- a/writeas.go +++ b/writeas.go @@ -191,7 +191,7 @@ func (c *Client) prepareRequest(r *http.Request) { if ua == "" { ua = "go-writeas v" + Version } - r.Header.Add("User-Agent", ua) + r.Header.Set("User-Agent", ua) r.Header.Add("Content-Type", "application/json") if c.token != "" { r.Header.Add("Authorization", "Token "+c.token) From a853522f69a0e3740db0725597ab6a506505342f Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 27 May 2019 09:04:23 -0700 Subject: [PATCH 18/22] adds GetCollectionPost this adds a client method to retrieve a collection post along with basic tests - phabricator task T588 --- collection.go | 24 ++++++++++++++++++++++++ collection_test.go | 17 +++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/collection.go b/collection.go index 273c37e..9b4a925 100644 --- a/collection.go +++ b/collection.go @@ -113,6 +113,30 @@ func (c *Client) GetCollectionPosts(alias string) (*[]Post, error) { } } +// GetCollectionPost retrieves a post from a collection +// and any error (in user-friendly form) that occurs). See +// https://developers.write.as/docs/api/#retrieve-a-collection-post +func (c *Client) GetCollectionPost(alias, slug string) (*Post, error) { + post := Post{} + + env, err := c.get(fmt.Sprintf("/collections/%s/posts/%s", alias, slug), &post) + if err != nil { + return nil, err + } + + if _, ok := env.Data.(*Post); !ok { + return nil, fmt.Errorf("Wrong data returned from API.") + } + + if env.Code == http.StatusOK { + return &post, nil + } else if env.Code == http.StatusNotFound { + return nil, fmt.Errorf("Post %s not found in collection %s", slug, alias) + } + + return nil, fmt.Errorf("Problem getting post %s from collection %s: %d. %v\n", slug, alias, env.Code, err) +} + // GetUserCollections retrieves the authenticated user's collections. // See https://developers.write.as/docs/api/#retrieve-user-39-s-collections func (c *Client) GetUserCollections() (*[]Collection, error) { diff --git a/collection_test.go b/collection_test.go index 93e82c7..5e8e9ef 100644 --- a/collection_test.go +++ b/collection_test.go @@ -34,6 +34,23 @@ func TestGetCollectionPosts(t *testing.T) { } } +func TestGetCollectionPost(t *testing.T) { + wac := NewClient() + + res, err := wac.GetCollectionPost("blog", "extending-write-as") + if err != nil { + t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) + } + + if res == nil { + t.Errorf("No post returned!") + } + + if len(res.Content) == 0 { + t.Errorf("Post content is empty!") + } +} + func TestGetUserCollections(t *testing.T) { wac := NewDevClient() _, err := wac.LogIn("demo", "demo") From 7c3a6949d56d4d91ad614c87067193f6ec8201a6 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 27 May 2019 09:07:29 -0700 Subject: [PATCH 19/22] fixes failing tests tests were failing on expected write.as blog title previously the title was 'write.as' and it is now 'write.as blog' also removed test case ExampleClient_GetCollection as was a duplicate of TestGetCollection --- collection_test.go | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/collection_test.go b/collection_test.go index 5e8e9ef..fe90ccc 100644 --- a/collection_test.go +++ b/collection_test.go @@ -15,7 +15,7 @@ func TestGetCollection(t *testing.T) { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } else { t.Logf("Collection: %+v", res) - if res.Title != "write.as" { + if res.Title != "write.as blog" { t.Errorf("Unexpected fetch results: %+v\n", res) } } @@ -107,14 +107,3 @@ func TestDeleteCollectionUnauthenticated(t *testing.T) { t.Fatalf("Error message should be more informative: %v", err) } } - -func ExampleClient_GetCollection() { - c := NewClient() - coll, err := c.GetCollection("blog") - if err != nil { - fmt.Printf("%v", err) - return - } - fmt.Printf("%s", coll.Title) - // Output: write.as -} From de8ce4dcd2f4994dfab2f97afe1e80e52924834b Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Tue, 28 May 2019 14:50:41 -0700 Subject: [PATCH 20/22] closes T589 update tests to use dev client this changes tests to only use the dev client as all code is shared between that and the write.as client, just the host changes. also updated post tests to round trip and test over sub tests --- collection_test.go | 56 +++++++++++------------ post_test.go | 129 +++++++++++++++++++++++------------------------------ 2 files changed, 83 insertions(+), 102 deletions(-) diff --git a/collection_test.go b/collection_test.go index fe90ccc..d6bb49b 100644 --- a/collection_test.go +++ b/collection_test.go @@ -8,47 +8,45 @@ import ( ) func TestGetCollection(t *testing.T) { - wac := NewClient() + dwac := NewDevClient() - res, err := wac.GetCollection("blog") + res, err := dwac.GetCollection("tester") if err != nil { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) - } else { - t.Logf("Collection: %+v", res) - if res.Title != "write.as blog" { - t.Errorf("Unexpected fetch results: %+v\n", res) - } + } + if res == nil { + t.Error("Expected collection to not be nil") } } func TestGetCollectionPosts(t *testing.T) { - wac := NewClient() + dwac := NewDevClient() + posts := []Post{} - res, err := wac.GetCollectionPosts("blog") - if err != nil { - t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) - } else { + t.Run("Get all posts in collection", func(t *testing.T) { + res, err := dwac.GetCollectionPosts("tester") + if err != nil { + t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) + } if len(*res) == 0 { - t.Errorf("No posts returned!") + t.Error("Expected at least on post in collection") + } + posts = *res + }) + t.Run("Get one post from collection", func(t *testing.T) { + res, err := dwac.GetCollectionPost("tester", posts[0].Slug) + if err != nil { + t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } - } -} - -func TestGetCollectionPost(t *testing.T) { - wac := NewClient() - - res, err := wac.GetCollectionPost("blog", "extending-write-as") - if err != nil { - t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) - } - if res == nil { - t.Errorf("No post returned!") - } + if res == nil { + t.Errorf("No post returned!") + } - if len(res.Content) == 0 { - t.Errorf("Post content is empty!") - } + if len(res.Content) == 0 { + t.Errorf("Post content is empty!") + } + }) } func TestGetUserCollections(t *testing.T) { diff --git a/post_test.go b/post_test.go index a7e3475..9d2fb47 100644 --- a/post_test.go +++ b/post_test.go @@ -2,82 +2,57 @@ package writeas import ( "fmt" - "strings" "testing" ) -func TestCreatePost(t *testing.T) { - wac := NewClient() - p, err := wac.CreatePost(&PostParams{ - Title: "Title!", - Content: "This is a post.", - Font: "sans", +func TestPostRoundTrip(t *testing.T) { + var id, token string + dwac := NewClient() + t.Run("Create post", func(t *testing.T) { + p, err := dwac.CreatePost(&PostParams{ + Title: "Title!", + Content: "This is a post.", + Font: "sans", + }) + if err != nil { + t.Errorf("Post create failed: %v", err) + return + } + t.Logf("Post created: %+v", p) + id, token = p.ID, p.Token }) - if err != nil { - t.Errorf("Post create failed: %v", err) - return - } - t.Logf("Post created: %+v", p) - - token := p.Token - - // Update post - p, err = wac.UpdatePost(p.ID, token, &PostParams{ - Content: "Now it's been updated!", + t.Run("Get post", func(t *testing.T) { + res, err := dwac.GetPost(id) + if err != nil { + t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) + } else { + t.Logf("Post: %+v", res) + if res.Content != "This is a post." { + t.Errorf("Unexpected fetch results: %+v\n", res) + } + } }) - if err != nil { - t.Errorf("Post update failed: %v", err) - return - } - t.Logf("Post updated: %+v", p) - - // Delete post - err = wac.DeletePost(p.ID, token) - if err != nil { - t.Errorf("Post delete failed: %v", err) - return - } - t.Logf("Post deleted!") -} - -func TestGetPost(t *testing.T) { - dwac := NewDevClient() - res, err := dwac.GetPost("zekk5r9apum6p") - if err != nil { - t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) - } else { - t.Logf("Post: %+v", res) - if res.Content != "This is a post." { - t.Errorf("Unexpected fetch results: %+v\n", res) + t.Run("Update post", func(t *testing.T) { + p, err := dwac.UpdatePost(id, token, &PostParams{ + Content: "Now it's been updated!", + }) + if err != nil { + t.Errorf("Post update failed: %v", err) + return } - } - - wac := NewClient() - res, err = wac.GetPost("3psnxyhqxy3hq") - if err != nil { - t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) - } else { - if !strings.HasPrefix(res.Content, " Write.as Blog") { - t.Errorf("Unexpected fetch results: %+v\n", res) + t.Logf("Post updated: %+v", p) + }) + t.Run("Delete post", func(t *testing.T) { + err := dwac.DeletePost(id, token) + if err != nil { + t.Errorf("Post delete failed: %v", err) + return } - } -} - -func TestPinPost(t *testing.T) { - dwac := NewDevClient() - _, err := dwac.LogIn("demo", "demo") - if err != nil { - t.Fatalf("Unable to log in: %v", err) - } - defer dwac.LogOut() - - err = dwac.PinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"}) - if err != nil { - t.Fatalf("Pin failed: %v", err) - } + t.Logf("Post deleted!") + }) } -func TestUnpinPost(t *testing.T) { +func TestPinUnPin(t *testing.T) { dwac := NewDevClient() _, err := dwac.LogIn("demo", "demo") if err != nil { @@ -85,17 +60,25 @@ func TestUnpinPost(t *testing.T) { } defer dwac.LogOut() - err = dwac.UnpinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"}) - if err != nil { - t.Fatalf("Unpin failed: %v", err) - } + t.Run("Pin post", func(t *testing.T) { + err := dwac.PinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"}) + if err != nil { + t.Fatalf("Pin failed: %v", err) + } + }) + t.Run("Unpin post", func(t *testing.T) { + err := dwac.UnpinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"}) + if err != nil { + t.Fatalf("Unpin failed: %v", err) + } + }) } func ExampleClient_CreatePost() { - c := NewClient() + dwac := NewDevClient() // Publish a post - p, err := c.CreatePost(&PostParams{ + p, err := dwac.CreatePost(&PostParams{ Title: "Title!", Content: "This is a post.", Font: "sans", From 268710536d308a8400361cb779a2bc6ebcf1f8cb Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 10 Jun 2019 12:51:20 -0700 Subject: [PATCH 21/22] use POST for claim posts endpoint --- post.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.go b/post.go index be6bdca..e27b612 100644 --- a/post.go +++ b/post.go @@ -212,7 +212,7 @@ func (c *Client) deletePost(collection, identifier, token string) error { // https://developer.write.as/docs/api/#claim-posts. func (c *Client) ClaimPosts(sp *[]OwnedPostParams) (*[]ClaimPostResult, error) { p := &[]ClaimPostResult{} - env, err := c.put("/posts/claim", sp, p) + env, err := c.post("/posts/claim", sp, p) if err != nil { return nil, err } From 064e8a36c8750fe8c5736bcfede6b1b3bec932ff Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 10 Jun 2019 14:14:38 -0700 Subject: [PATCH 22/22] do not exclude ID in OwnedPostParams --- post.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.go b/post.go index e27b612..1f8a55b 100644 --- a/post.go +++ b/post.go @@ -31,7 +31,7 @@ type ( // OwnedPostParams are, together, fields only the original post author knows. OwnedPostParams struct { - ID string `json:"-"` + ID string `json:"id"` Token string `json:"token,omitempty"` }