Page MenuHomeMusing Studio

No OneTemporary

diff --git a/auth.go b/auth.go
index 26b5eb9..8fbedee 100644
--- a/auth.go
+++ b/auth.go
@@ -1,75 +1,76 @@
package writeas
import (
+ "context"
"fmt"
"net/http"
)
// LogIn authenticates a user with Write.as.
// See https://developers.write.as/docs/api/#authenticate-a-user
-func (c *Client) LogIn(username, pass string) (*AuthUser, error) {
+func (c *Client) LogIn(ctx context.Context, username, pass string) (*AuthUser, error) {
u := &AuthUser{}
up := struct {
Alias string `json:"alias"`
Pass string `json:"pass"`
}{
Alias: username,
Pass: pass,
}
- env, err := c.post("/auth/login", up, u)
+ env, err := c.post(ctx, "/auth/login", up, u)
if err != nil {
return nil, err
}
var ok bool
if u, ok = env.Data.(*AuthUser); !ok {
return nil, fmt.Errorf("Wrong data returned from API.")
}
status := env.Code
if status != http.StatusOK {
if status == http.StatusBadRequest {
return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
} else if status == http.StatusUnauthorized {
return nil, fmt.Errorf("Incorrect password.")
} else if status == http.StatusNotFound {
return nil, fmt.Errorf("User does not exist.")
} else if status == http.StatusTooManyRequests {
return nil, fmt.Errorf("Too many log in attempts in a short period of time.")
}
return nil, fmt.Errorf("Problem authenticating: %d. %v\n", status, err)
}
c.SetToken(u.AccessToken)
return u, nil
}
// LogOut logs the current user out, making the Client's current access token
// invalid.
-func (c *Client) LogOut() error {
- env, err := c.delete("/auth/me", nil)
+func (c *Client) LogOut(ctx context.Context) error {
+ env, err := c.delete(ctx, "/auth/me", nil)
if err != nil {
return err
}
status := env.Code
if status != http.StatusNoContent {
if status == http.StatusNotFound {
return fmt.Errorf("Access token is invalid or doesn't exist")
}
return fmt.Errorf("Unable to log out: %v", env.ErrorMessage)
}
// Logout successful, so update the Client
c.token = ""
return nil
}
func (c *Client) isNotLoggedIn(code int) bool {
if c.token == "" {
return false
}
return code == http.StatusUnauthorized
}
diff --git a/auth_test.go b/auth_test.go
index a9ece6f..063e963 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -1,19 +1,23 @@
package writeas
-import "testing"
+import (
+ "context"
+ "testing"
+)
func TestAuthentication(t *testing.T) {
dwac := NewDevClient()
+ ctx := context.Background()
// Log in
- _, err := dwac.LogIn("demo", "demo")
+ _, err := dwac.LogIn(ctx, "demo", "demo")
if err != nil {
t.Fatalf("Unable to log in: %v", err)
}
// Log out
- err = dwac.LogOut()
+ err = dwac.LogOut(ctx)
if err != nil {
t.Fatalf("Unable to log out: %v", err)
}
}
diff --git a/author.go b/author.go
index 06af35c..3b3fe10 100644
--- a/author.go
+++ b/author.go
@@ -1,54 +1,55 @@
package writeas
import (
+ "context"
"fmt"
"net/http"
)
type (
// Author represents a Write.as author.
Author struct {
User *User
Name string `json:"name"`
Slug string `json:"slug"`
}
// AuthorParams are used to create or update a Write.as author.
AuthorParams struct {
// Name is the public display name of the Author.
Name string `json:"name"`
// Slug is the optional slug for the Author.
Slug string `json:"slug"`
// OrgAlias is the alias of the organization the Author belongs to.
OrgAlias string `json:"-"`
}
)
// CreateContributor creates a new contributor on the given organization.
-func (c *Client) CreateContributor(sp *AuthorParams) (*Author, error) {
+func (c *Client) CreateContributor(ctx context.Context, sp *AuthorParams) (*Author, error) {
if sp.OrgAlias == "" {
return nil, fmt.Errorf("AuthorParams.OrgAlias is required.")
}
a := &Author{}
- env, err := c.post("/organizations/"+sp.OrgAlias+"/contributors", sp, a)
+ env, err := c.post(ctx, "/organizations/"+sp.OrgAlias+"/contributors", sp, a)
if err != nil {
return nil, err
}
var ok bool
if a, ok = env.Data.(*Author); !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 author: %d. %s\n", status, env.ErrorMessage)
}
return a, nil
}
diff --git a/author_test.go b/author_test.go
index fa8b08c..cd6bd0d 100644
--- a/author_test.go
+++ b/author_test.go
@@ -1,37 +1,41 @@
package writeas
-import "testing"
+import (
+ "context"
+ "testing"
+)
func TestClient_CreateContributor(t *testing.T) {
c := NewClientWith(Config{URL: "http://localhost:7777/api"})
- _, err := c.LogIn("test", "test")
+ ctx := context.Background()
+ _, err := c.LogIn(ctx, "test", "test")
if err != nil {
t.Fatalf("login: %s", err)
}
tests := []struct {
name string
AName string
ASlug string
AOrg string
}{
{
name: "good",
AName: "Bob Contrib",
ASlug: "bob",
AOrg: "write-as",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
- _, err = c.CreateContributor(&AuthorParams{
+ _, err = c.CreateContributor(ctx, &AuthorParams{
Name: test.AName,
Slug: test.ASlug,
OrgAlias: test.AOrg,
})
if err != nil {
t.Fatalf("create %s: %s", test.name, err)
}
})
}
}
diff --git a/collection.go b/collection.go
index 3760ccd..fa389e7 100644
--- a/collection.go
+++ b/collection.go
@@ -1,190 +1,191 @@
package writeas
import (
+ "context"
"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"`
}
// 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) {
+func (c *Client) CreateCollection(ctx context.Context, sp *CollectionParams) (*Collection, error) {
p := &Collection{}
- env, err := c.post("/collections", sp, p)
+ env, err := c.post(ctx, "/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) {
+func (c *Client) GetCollection(ctx context.Context, alias string) (*Collection, error) {
coll := &Collection{}
- env, err := c.get(fmt.Sprintf("/collections/%s", alias), coll)
+ env, err := c.get(ctx, 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, page int) (*[]Post, error) {
+func (c *Client) GetCollectionPosts(ctx context.Context, alias string, page int) (*[]Post, error) {
coll := &Collection{}
q := ""
if page > 0 {
q = fmt.Sprintf("?page=%d", page)
}
- env, err := c.get(fmt.Sprintf("/collections/%s/posts%s", alias, q), coll)
+ env, err := c.get(ctx, fmt.Sprintf("/collections/%s/posts%s", alias, q), 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) {
+func (c *Client) GetCollectionPost(ctx context.Context, alias, slug string) (*Post, error) {
post := Post{}
- env, err := c.get(fmt.Sprintf("/collections/%s/posts/%s", alias, slug), &post)
+ env, err := c.get(ctx, 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) {
+func (c *Client) GetUserCollections(ctx context.Context) (*[]Collection, error) {
colls := &[]Collection{}
- env, err := c.get("/me/collections", colls)
+ env, err := c.get(ctx, "/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.")
}
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 {
+func (c *Client) DeleteCollection(ctx context.Context, alias string) error {
endpoint := "/collections/" + alias
- env, err := c.delete(endpoint, nil /* data */)
+ env, err := c.delete(ctx, 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..ae4e462 100644
--- a/collection_test.go
+++ b/collection_test.go
@@ -1,107 +1,111 @@
package writeas
import (
+ "context"
"fmt"
"strings"
"testing"
"time"
)
func TestGetCollection(t *testing.T) {
dwac := NewDevClient()
- res, err := dwac.GetCollection("tester")
+ res, err := dwac.GetCollection(context.Background(), "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{}
+ ctx := context.Background()
t.Run("Get all posts in collection", func(t *testing.T) {
- res, err := dwac.GetCollectionPosts("tester")
+ res, err := dwac.GetCollectionPosts(ctx, "tester")
if err != nil {
t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
}
if len(*res) == 0 {
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)
+ res, err := dwac.GetCollectionPost(ctx, "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")
+ ctx := context.Background()
+ _, err := wac.LogIn(ctx, "demo", "demo")
if err != nil {
t.Fatalf("Unable to log in: %v", err)
}
- defer wac.LogOut()
+ defer wac.LogOut(ctx)
- res, err := wac.GetUserCollections()
+ res, err := wac.GetUserCollections(ctx)
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 {
t.Errorf("No collections returned!")
}
}
}
func TestCreateAndDeleteCollection(t *testing.T) {
wac := NewDevClient()
- _, err := wac.LogIn("demo", "demo")
+ ctx := context.Background()
+ _, err := wac.LogIn(ctx, "demo", "demo")
if err != nil {
t.Fatalf("Unable to log in: %v", err)
}
- defer wac.LogOut()
+ defer wac.LogOut(ctx)
now := time.Now().Unix()
alias := fmt.Sprintf("test-collection-%v", now)
- c, err := wac.CreateCollection(&CollectionParams{
+ c, err := wac.CreateCollection(ctx, &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 {
+ if err := wac.DeleteCollection(ctx, 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)
+ err := wac.DeleteCollection(context.Background(), 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/formatting.go b/formatting.go
index 5a85aee..f721044 100644
--- a/formatting.go
+++ b/formatting.go
@@ -1,39 +1,40 @@
package writeas
import (
+ "context"
"fmt"
"net/http"
)
type BodyResponse struct {
Body string `json:"body"`
}
// Markdown takes raw Markdown and renders it into usable HTML. See
// https://developers.write.as/docs/api/#render-markdown.
-func (c *Client) Markdown(body, collectionURL string) (string, error) {
+func (c *Client) Markdown(ctx context.Context, body, collectionURL string) (string, error) {
p := &BodyResponse{}
data := struct {
RawBody string `json:"raw_body"`
CollectionURL string `json:"collection_url,omitempty"`
}{
RawBody: body,
CollectionURL: collectionURL,
}
- env, err := c.post("/markdown", data, p)
+ env, err := c.post(ctx, "/markdown", data, p)
if err != nil {
return "", err
}
var ok bool
if p, ok = env.Data.(*BodyResponse); !ok {
return "", fmt.Errorf("Wrong data returned from API.")
}
status := env.Code
if status != http.StatusOK {
return "", fmt.Errorf("Problem getting markdown: %d. %s\n", status, env.ErrorMessage)
}
return p.Body, nil
}
diff --git a/formatting_test.go b/formatting_test.go
index 0a83844..668e784 100644
--- a/formatting_test.go
+++ b/formatting_test.go
@@ -1,23 +1,24 @@
package writeas
import (
+ "context"
"testing"
)
func TestMarkdown(t *testing.T) {
dwac := NewDevClient()
in := "This is *formatted* in __Markdown__."
out := `<p>This is <em>formatted</em> in <strong>Markdown</strong>.</p>
`
- res, err := dwac.Markdown(in, "")
+ res, err := dwac.Markdown(context.Background(), in, "")
if err != nil {
t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
}
if res != out {
t.Errorf(`Got: '%s'
Expected: '%s'`, res, out)
}
}
diff --git a/post.go b/post.go
index a98269e..22de9cd 100644
--- a/post.go
+++ b/post.go
@@ -1,344 +1,345 @@
package writeas
import (
+ "context"
"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"`
Type PostType `json:"type"`
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"`
AuthorSlug *string `json:"author,omitempty"`
Categories []Category `json:"categories,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"`
}
)
type PostType string
const (
TypePost PostType = "post"
TypePrompt = "prompt"
TypePromptArchive = "prompt-arch"
TypeSubmission = "submission"
TypeSubmissionDraft = "submission-draft"
)
// 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) {
+func (c *Client) GetPost(ctx context.Context, id string) (*Post, error) {
p := &Post{}
- env, err := c.get(fmt.Sprintf("/posts/%s", id), p)
+ env, err := c.get(ctx, 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) {
+func (c *Client) CreatePost(ctx context.Context, sp *PostParams) (*Post, error) {
p := &Post{}
endPre := ""
if sp.Collection != "" {
endPre = "/collections/" + sp.Collection
}
- env, err := c.post(endPre+"/posts", sp, p)
+ env, err := c.post(ctx, 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(ctx context.Context, id, token string, sp *PostParams) (*Post, error) {
+ return c.updatePost(ctx, "", id, token, sp)
}
-func (c *Client) updatePost(collection, identifier, token string, sp *PostParams) (*Post, error) {
+func (c *Client) updatePost(ctx context.Context, 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)
+ env, err := c.put(ctx, 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(ctx context.Context, id, token string) error {
+ return c.deletePost(ctx, "", id, token)
}
-func (c *Client) deletePost(collection, identifier, token string) error {
+func (c *Client) deletePost(ctx context.Context, 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)
+ env, err := c.delete(ctx, 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) {
+func (c *Client) ClaimPosts(ctx context.Context, sp *[]OwnedPostParams) (*[]ClaimPostResult, error) {
p := &[]ClaimPostResult{}
- env, err := c.post("/posts/claim", sp, p)
+ env, err := c.post(ctx, "/posts/claim", sp, p)
if err != nil {
return nil, err
}
var ok bool
if p, ok = env.Data.(*[]ClaimPostResult); !ok {
return nil, fmt.Errorf("Wrong data returned from API.")
}
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) {
+func (c *Client) GetUserPosts(ctx context.Context) (*[]Post, error) {
p := &[]Post{}
- env, err := c.get("/me/posts", p)
+ env, err := c.get(ctx, "/me/posts", 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.")
}
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 {
+func (c *Client) PinPost(ctx context.Context, alias string, pp *PinnedPostParams) error {
res := &[]BatchPostResult{}
- env, err := c.post(fmt.Sprintf("/collections/%s/pin", alias), []*PinnedPostParams{pp}, res)
+ env, err := c.post(ctx, 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 {
+func (c *Client) UnpinPost(ctx context.Context, alias string, pp *PinnedPostParams) error {
res := &[]BatchPostResult{}
- env, err := c.post(fmt.Sprintf("/collections/%s/unpin", alias), []*PinnedPostParams{pp}, res)
+ env, err := c.post(ctx, 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
}
diff --git a/post_test.go b/post_test.go
index 9d2fb47..7a0c8cc 100644
--- a/post_test.go
+++ b/post_test.go
@@ -1,93 +1,96 @@
package writeas
import (
+ "context"
"fmt"
"testing"
)
func TestPostRoundTrip(t *testing.T) {
var id, token string
dwac := NewClient()
+ ctx := context.Background()
t.Run("Create post", func(t *testing.T) {
- p, err := dwac.CreatePost(&PostParams{
+ p, err := dwac.CreatePost(ctx, &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
})
t.Run("Get post", func(t *testing.T) {
- res, err := dwac.GetPost(id)
+ res, err := dwac.GetPost(ctx, 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)
}
}
})
t.Run("Update post", func(t *testing.T) {
- p, err := dwac.UpdatePost(id, token, &PostParams{
+ p, err := dwac.UpdatePost(ctx, id, token, &PostParams{
Content: "Now it's been updated!",
})
if err != nil {
t.Errorf("Post update failed: %v", err)
return
}
t.Logf("Post updated: %+v", p)
})
t.Run("Delete post", func(t *testing.T) {
- err := dwac.DeletePost(id, token)
+ err := dwac.DeletePost(ctx, id, token)
if err != nil {
t.Errorf("Post delete failed: %v", err)
return
}
t.Logf("Post deleted!")
})
}
func TestPinUnPin(t *testing.T) {
dwac := NewDevClient()
- _, err := dwac.LogIn("demo", "demo")
+ ctx := context.Background()
+ _, err := dwac.LogIn(ctx, "demo", "demo")
if err != nil {
t.Fatalf("Unable to log in: %v", err)
}
- defer dwac.LogOut()
+ defer dwac.LogOut(ctx)
t.Run("Pin post", func(t *testing.T) {
- err := dwac.PinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"})
+ err := dwac.PinPost(ctx, "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"})
+ err := dwac.UnpinPost(ctx, "tester", &PinnedPostParams{ID: "olx6uk7064heqltf"})
if err != nil {
t.Fatalf("Unpin failed: %v", err)
}
})
}
func ExampleClient_CreatePost() {
dwac := NewDevClient()
// Publish a post
- p, err := dwac.CreatePost(&PostParams{
+ p, err := dwac.CreatePost(context.Background(), &PostParams{
Title: "Title!",
Content: "This is a post.",
Font: "sans",
})
if err != nil {
fmt.Printf("Unable to create: %v", err)
return
}
fmt.Printf("%s", p.Content)
// Output: This is a post.
}
diff --git a/user.go b/user.go
index 48143ef..9e5c24e 100644
--- a/user.go
+++ b/user.go
@@ -1,68 +1,69 @@
package writeas
import (
+ "context"
"fmt"
"net/http"
"time"
)
type (
// AuthUser represents a just-authenticated user. It contains information
// that'll only be returned once (now) per user session.
AuthUser struct {
AccessToken string `json:"access_token,omitempty"`
Password string `json:"password,omitempty"`
User *User `json:"user"`
}
// User represents a registered Write.as user.
User struct {
Username string `json:"username"`
Email string `json:"email"`
Created time.Time `json:"created"`
// Optional properties
Subscription *UserSubscription `json:"subscription"`
}
// UserSubscription contains information about a user's Write.as
// subscription.
UserSubscription struct {
Name string `json:"name"`
Begin time.Time `json:"begin"`
End time.Time `json:"end"`
AutoRenew bool `json:"auto_renew"`
Active bool `json:"is_active"`
Delinquent bool `json:"is_delinquent"`
}
)
// GetMe retrieves the authenticated User's information.
// See: https://developers.write.as/docs/api/#retrieve-authenticated-user
-func (c *Client) GetMe(verbose bool) (*User, error) {
+func (c *Client) GetMe(ctx context.Context, verbose bool) (*User, error) {
if c.Token() == "" {
return nil, fmt.Errorf("Unable to get user; no access token given.")
}
params := ""
if verbose {
params = "?verbose=true"
}
- env, err := c.get("/me"+params, nil)
+ env, err := c.get(ctx, "/me"+params, nil)
if err != nil {
return nil, err
}
status := env.Code
if status == http.StatusUnauthorized {
return nil, fmt.Errorf("invalid or expired token")
}
var u *User
var ok bool
if u, ok = env.Data.(*User); !ok {
return nil, fmt.Errorf("Wrong data returned from API.")
}
return u, nil
}
diff --git a/writeas.go b/writeas.go
index 021255e..bfae77a 100644
--- a/writeas.go
+++ b/writeas.go
@@ -1,219 +1,220 @@
// Package writeas provides the binding for the Write.as API
package writeas
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"code.as/core/socks"
"github.com/writeas/impart"
)
const (
apiURL = "https://write.as/api"
devAPIURL = "https://development.write.as/api"
torAPIURL = "http://writeasw4b635r4o3vec6mu45s47ohfyro5vayzx2zjwod4pjswyovyd.onion/api"
// Current go-writeas version
Version = "2"
)
// Client is used to interact with the Write.as API. It can be used to make
// authenticated or unauthenticated calls.
type Client struct {
baseURL string
// Access token for the user making requests.
token string
// Application-level API key.
apiKey string
// Client making requests to the API
client *http.Client
// UserAgent overrides the default User-Agent header
UserAgent string
}
// defaultHTTPTimeout is the default http.Client timeout.
const defaultHTTPTimeout = 10 * time.Second
// NewClient creates a new API client. By default, all requests are made
// unauthenticated. To optionally make authenticated requests, call `SetToken`.
//
-// c := writeas.NewClient()
-// c.SetToken("00000000-0000-0000-0000-000000000000")
+// c := writeas.NewClient()
+// c.SetToken("00000000-0000-0000-0000-000000000000")
func NewClient() *Client {
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 {
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: httpClient,
baseURL: c.URL,
token: c.Token,
}
}
// SetToken sets the user token for all future Client requests. Setting this to
// an empty string will change back to unauthenticated requests.
func (c *Client) SetToken(token string) {
c.token = token
}
// SetApplicationKey sets an application-level API key for all Client requests.
func (c *Client) SetApplicationKey(key string) {
c.apiKey = key
}
// SetClient sets a custom http.Client to use instead of the default.
func (c *Client) SetClient(cl *http.Client) {
c.client = cl
}
// Token returns the user token currently set to the Client.
func (c *Client) Token() string {
return c.token
}
// BaseURL returns the base API URL the Client will make calls against.
func (c *Client) BaseURL() string {
return c.baseURL
}
-func (c *Client) get(path string, r interface{}) (*impart.Envelope, error) {
+func (c *Client) get(ctx context.Context, path string, r interface{}) (*impart.Envelope, error) {
method := "GET"
if method != "GET" && method != "HEAD" {
return nil, fmt.Errorf("Method %s not currently supported by library (only HEAD and GET).\n", method)
}
- return c.request(method, path, nil, r)
+ return c.request(ctx, method, path, nil, r)
}
-func (c *Client) post(path string, data, r interface{}) (*impart.Envelope, error) {
+func (c *Client) post(ctx context.Context, path string, data, r interface{}) (*impart.Envelope, error) {
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(data)
- return c.request("POST", path, b, r)
+ return c.request(ctx, "POST", path, b, r)
}
-func (c *Client) put(path string, data, r interface{}) (*impart.Envelope, error) {
+func (c *Client) put(ctx context.Context, path string, data, r interface{}) (*impart.Envelope, error) {
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(data)
- return c.request("PUT", path, b, r)
+ return c.request(ctx, "PUT", path, b, r)
}
-func (c *Client) delete(path string, data map[string]string) (*impart.Envelope, error) {
- r, err := c.buildRequest("DELETE", path, nil)
+func (c *Client) delete(ctx context.Context, path string, data map[string]string) (*impart.Envelope, error) {
+ r, err := c.buildRequest(ctx, "DELETE", path, nil)
if err != nil {
return nil, err
}
q := r.URL.Query()
for k, v := range data {
q.Add(k, v)
}
r.URL.RawQuery = q.Encode()
return c.doRequest(r, nil)
}
-func (c *Client) request(method, path string, data io.Reader, result interface{}) (*impart.Envelope, error) {
- r, err := c.buildRequest(method, path, data)
+func (c *Client) request(ctx context.Context, method, path string, data io.Reader, result interface{}) (*impart.Envelope, error) {
+ r, err := c.buildRequest(ctx, method, path, data)
if err != nil {
return nil, err
}
return c.doRequest(r, result)
}
-func (c *Client) buildRequest(method, path string, data io.Reader) (*http.Request, error) {
+func (c *Client) buildRequest(ctx context.Context, method, path string, data io.Reader) (*http.Request, error) {
url := fmt.Sprintf("%s%s", c.baseURL, path)
- r, err := http.NewRequest(method, url, data)
+ r, err := http.NewRequestWithContext(ctx, method, url, data)
if err != nil {
return nil, fmt.Errorf("Create request: %v", err)
}
c.prepareRequest(r)
return r, nil
}
func (c *Client) doRequest(r *http.Request, result interface{}) (*impart.Envelope, error) {
resp, err := c.client.Do(r)
if err != nil {
return nil, fmt.Errorf("Request: %v", err)
}
defer resp.Body.Close()
env := &impart.Envelope{
Code: resp.StatusCode,
}
if result != nil {
env.Data = result
err = json.NewDecoder(resp.Body).Decode(&env)
if err != nil {
return nil, err
}
}
return env, nil
}
func (c *Client) prepareRequest(r *http.Request) {
ua := c.UserAgent
if ua == "" {
ua = "go-writeas v" + Version
}
r.Header.Set("User-Agent", ua)
r.Header.Add("Content-Type", "application/json")
if c.token != "" {
r.Header.Add("Authorization", "Token "+c.token)
}
if c.apiKey != "" {
r.Header.Add("X-API-Key", c.apiKey)
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 1:04 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3104544

Event Timeline