Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10384334
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
40 KB
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 1:04 PM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3104544
Attached To
rWGO writeas-go
Event Timeline
Log In to Comment