diff --git a/collection.go b/collection.go index da44638..2a68f92 100644 --- a/collection.go +++ b/collection.go @@ -1,186 +1,188 @@ package writeas import ( "fmt" "net/http" ) type ( // Collection represents a collection of posts. Blogs are a type of collection // on Write.as. Collection struct { Alias string `json:"alias"` Title string `json:"title"` Description string `json:"description"` StyleSheet string `json:"style_sheet"` Private bool `json:"private"` Views int64 `json:"views"` Domain string `json:"domain,omitempty"` Email string `json:"email,omitempty"` URL string `json:"url,omitempty"` TotalPosts int `json:"total_posts"` - Posts *[]Post `json:"posts,omitempty"` + Posts []Post `json:"posts,omitempty"` } // CollectionParams holds values for creating a collection. CollectionParams struct { Alias string `json:"alias"` Title string `json:"title"` Description string `json:"description,omitempty"` } ) // CreateCollection creates a new collection, returning a user-friendly error // if one comes up. Requires a Write.as subscription. See // https://developers.write.as/docs/api/#create-a-collection func (c *Client) CreateCollection(sp *CollectionParams) (*Collection, error) { p := &Collection{} env, err := c.post("/collections", sp, p) if err != nil { return nil, err } var ok bool if p, ok = env.Data.(*Collection); !ok { return nil, fmt.Errorf("Wrong data returned from API.") } status := env.Code if status != http.StatusCreated { if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } else if status == http.StatusForbidden { return nil, fmt.Errorf("Casual or Pro user required.") } else if status == http.StatusConflict { return nil, fmt.Errorf("Collection name is already taken.") } else if status == http.StatusPreconditionFailed { return nil, fmt.Errorf("Reached max collection quota.") } return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err) } return p, nil } // GetCollection retrieves a collection, returning the Collection and any error // (in user-friendly form) that occurs. See // https://developers.write.as/docs/api/#retrieve-a-collection func (c *Client) GetCollection(alias string) (*Collection, error) { coll := &Collection{} env, err := c.get(fmt.Sprintf("/collections/%s", alias), coll) if err != nil { return nil, err } var ok bool if coll, ok = env.Data.(*Collection); !ok { return nil, fmt.Errorf("Wrong data returned from API.") } status := env.Code if status == http.StatusOK { return coll, nil } else if status == http.StatusNotFound { return nil, fmt.Errorf("Collection not found.") } else { return nil, fmt.Errorf("Problem getting collection: %d. %v\n", status, err) } } // GetCollectionPosts retrieves a collection's posts, returning the Posts // and any error (in user-friendly form) that occurs. See // https://developers.write.as/docs/api/#retrieve-collection-posts -func (c *Client) GetCollectionPosts(alias string) (*[]Post, error) { +func (c *Client) GetCollectionPosts(alias string) ([]Post, error) { coll := &Collection{} env, err := c.get(fmt.Sprintf("/collections/%s/posts", alias), coll) if err != nil { return nil, err } var ok bool if coll, ok = env.Data.(*Collection); !ok { return nil, fmt.Errorf("Wrong data returned from API.") } status := env.Code if status == http.StatusOK { return coll.Posts, nil } else if status == http.StatusNotFound { return nil, fmt.Errorf("Collection not found.") } else { return nil, fmt.Errorf("Problem getting collection: %d. %v\n", status, err) } } // 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) { - colls := &[]Collection{} - env, err := c.get("/me/collections", colls) +func (c *Client) GetUserCollections() ([]Collection, error) { + colls := []Collection{} + env, err := c.get("/me/collections", &colls) if err != nil { return nil, err } var ok bool - if colls, ok = env.Data.(*[]Collection); !ok { - return nil, fmt.Errorf("Wrong data returned from API.") + collsNew, ok := env.Data.(*[]Collection) + if !ok { + return nil, fmt.Errorf("Wrong data returned from API. '%+v'", env) } + colls = *collsNew status := env.Code if status != http.StatusOK { if c.isNotLoggedIn(status) { return nil, fmt.Errorf("Not authenticated.") } return nil, fmt.Errorf("Problem getting collections: %d. %v\n", status, err) } 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 d6bb49b..d5a6cb0 100644 --- a/collection_test.go +++ b/collection_test.go @@ -1,107 +1,107 @@ package writeas import ( "fmt" "strings" "testing" "time" ) func TestGetCollection(t *testing.T) { dwac := NewDevClient() res, err := dwac.GetCollection("tester") if err != nil { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } if res == nil { t.Error("Expected collection to not be nil") } } func TestGetCollectionPosts(t *testing.T) { dwac := NewDevClient() posts := []Post{} 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 { + if len(res) == 0 { t.Error("Expected at least on post in collection") } - posts = *res + 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) } 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") if err != nil { t.Fatalf("Unable to log in: %v", err) } defer wac.LogOut() res, err := wac.GetUserCollections() if err != nil { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } else { t.Logf("User collections: %+v", res) - if len(*res) == 0 { + if len(res) == 0 { t.Errorf("No collections returned!") } } } 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) } } diff --git a/post.go b/post.go index 33d650f..4178eee 100644 --- a/post.go +++ b/post.go @@ -1,330 +1,334 @@ package writeas import ( "fmt" "net/http" "time" ) type ( // Post represents a published Write.as post, whether anonymous, owned by a // user, or part of a collection. Post struct { ID string `json:"id"` Slug string `json:"slug"` Token string `json:"token"` Font string `json:"appearance"` Language *string `json:"language"` RTL *bool `json:"rtl"` Listed bool `json:"listed"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` Title string `json:"title"` Content string `json:"body"` Views int64 `json:"views"` Tags []string `json:"tags"` Images []string `json:"images"` OwnerName string `json:"owner,omitempty"` Collection *Collection `json:"collection,omitempty"` } // OwnedPostParams are, together, fields only the original post author knows. OwnedPostParams struct { ID string `json:"id"` Token string `json:"token,omitempty"` } // PostParams holds values for creating or updating a post. PostParams struct { // Parameters only for updating ID string `json:"-"` Token string `json:"token,omitempty"` // Parameters for creating or updating 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"` // Parameters for collection posts Collection string `json:"-"` } // PinnedPostParams holds values for pinning a post PinnedPostParams struct { ID string `json:"id"` Position int `json:"position"` } // BatchPostResult contains the post-specific result as part of a larger // batch operation. BatchPostResult struct { ID string `json:"id,omitempty"` Code int `json:"code,omitempty"` ErrorMessage string `json:"error_msg,omitempty"` } // ClaimPostResult contains the post-specific result for a request to // associate a post to an account. ClaimPostResult struct { ID string `json:"id,omitempty"` Code int `json:"code,omitempty"` ErrorMessage string `json:"error_msg,omitempty"` Post *Post `json:"post,omitempty"` } ) // GetPost retrieves a published post, returning the Post and any error (in // user-friendly form) that occurs. See // https://developers.write.as/docs/api/#retrieve-a-post. func (c *Client) GetPost(id string) (*Post, error) { p := &Post{} env, err := c.get(fmt.Sprintf("/posts/%s", id), p) if err != nil { return nil, err } var ok bool if p, ok = env.Data.(*Post); !ok { return nil, fmt.Errorf("Wrong data returned from API.") } status := env.Code if status == http.StatusOK { return p, nil } else if status == http.StatusNotFound { return nil, fmt.Errorf("Post not found.") } else if status == http.StatusGone { return nil, fmt.Errorf("Post unpublished.") } 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 // up. See https://developers.write.as/docs/api/#publish-a-post. func (c *Client) CreatePost(sp *PostParams) (*Post, error) { p := &Post{} endPre := "" if sp.Collection != "" { endPre = "/collections/" + sp.Collection } env, err := c.post(endPre+"/posts", sp, p) if err != nil { return nil, err } var ok bool if p, ok = env.Data.(*Post); !ok { return nil, fmt.Errorf("Wrong data returned from API.") } status := env.Code 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. %s\n", status, env.ErrorMessage) } return p, nil } // UpdatePost updates a published post with the given PostParams. See // https://developers.write.as/docs/api/#update-a-post. 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{} endpoint := "/posts/" + identifier /* 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 } var ok bool if p, ok = env.Data.(*Post); !ok { return nil, fmt.Errorf("Wrong data returned from API.") } status := env.Code if status != http.StatusOK { if c.isNotLoggedIn(status) { return nil, fmt.Errorf("Not authenticated.") } else if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } return nil, fmt.Errorf("Problem updating post: %d. %s\n", status, env.ErrorMessage) } return p, nil } // DeletePost permanently deletes a published post. See // https://developers.write.as/docs/api/#delete-a-post. 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 } status := env.Code if status == http.StatusNoContent { return nil } else if c.isNotLoggedIn(status) { return fmt.Errorf("Not authenticated.") } else if status == http.StatusBadRequest { return fmt.Errorf("Bad request: %s", env.ErrorMessage) } return fmt.Errorf("Problem deleting post: %d. %s\n", status, env.ErrorMessage) } // ClaimPosts associates anonymous posts with a user / account. // https://developers.write.as/docs/api/#claim-posts. -func (c *Client) ClaimPosts(sp *[]OwnedPostParams) (*[]ClaimPostResult, error) { - p := &[]ClaimPostResult{} - env, err := c.post("/posts/claim", sp, p) +func (c *Client) ClaimPosts(sp []OwnedPostParams) ([]ClaimPostResult, error) { + p := []ClaimPostResult{} + env, err := c.post("/posts/claim", sp, &p) if err != nil { return nil, err } var ok bool - if p, ok = env.Data.(*[]ClaimPostResult); !ok { + newP, ok := env.Data.(*[]ClaimPostResult) + if !ok { return nil, fmt.Errorf("Wrong data returned from API.") } + p = *newP status := env.Code if status == http.StatusOK { return p, nil } else if c.isNotLoggedIn(status) { return nil, fmt.Errorf("Not authenticated.") } else if status == http.StatusBadRequest { return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage) } else { return nil, fmt.Errorf("Problem claiming post: %d. %s\n", status, env.ErrorMessage) } // TODO: does this also happen with moving posts? } // GetUserPosts retrieves the authenticated user's posts. // See https://developers.write.as/docs/api/#retrieve-user-39-s-posts -func (c *Client) GetUserPosts() (*[]Post, error) { - p := &[]Post{} - env, err := c.get("/me/posts", p) +func (c *Client) GetUserPosts() ([]Post, error) { + p := []Post{} + env, err := c.get("/me/posts", &p) if err != nil { return nil, err } var ok bool - if p, ok = env.Data.(*[]Post); !ok { + newP, ok := env.Data.(*[]Post) + if !ok { return nil, fmt.Errorf("Wrong data returned from API.") } + p = *newP status := env.Code if status != http.StatusOK { if c.isNotLoggedIn(status) { return nil, fmt.Errorf("Not authenticated.") } return nil, fmt.Errorf("Problem getting user posts: %d. %s\n", status, env.ErrorMessage) } return p, nil } // PinPost pins a post in the given collection. // See https://developers.write.as/docs/api/#pin-a-post-to-a-collection func (c *Client) PinPost(alias string, pp *PinnedPostParams) error { res := &[]BatchPostResult{} env, err := c.post(fmt.Sprintf("/collections/%s/pin", alias), []*PinnedPostParams{pp}, res) if err != nil { return err } var ok bool if res, ok = env.Data.(*[]BatchPostResult); !ok { return fmt.Errorf("Wrong data returned from API.") } // Check for basic request errors on top level response status := env.Code if status != http.StatusOK { if c.isNotLoggedIn(status) { return fmt.Errorf("Not authenticated.") } return fmt.Errorf("Problem pinning post: %d. %s\n", status, env.ErrorMessage) } // Check the individual post result if len(*res) == 0 || len(*res) > 1 { return fmt.Errorf("Wrong data returned from API.") } 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: %s", res[0].ErrorMessage) } return nil } // UnpinPost unpins a post from the given collection. // See https://developers.write.as/docs/api/#unpin-a-post-from-a-collection func (c *Client) UnpinPost(alias string, pp *PinnedPostParams) error { res := &[]BatchPostResult{} env, err := c.post(fmt.Sprintf("/collections/%s/unpin", alias), []*PinnedPostParams{pp}, res) if err != nil { return err } var ok bool if res, ok = env.Data.(*[]BatchPostResult); !ok { return fmt.Errorf("Wrong data returned from API.") } // Check for basic request errors on top level response status := env.Code if status != http.StatusOK { if c.isNotLoggedIn(status) { return fmt.Errorf("Not authenticated.") } return fmt.Errorf("Problem unpinning post: %d. %s\n", status, env.ErrorMessage) } // Check the individual post result if len(*res) == 0 || len(*res) > 1 { return fmt.Errorf("Wrong data returned from API.") } 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: %s", res[0].ErrorMessage) } return nil }