Browse Source

Merge branch 'v2'

Matt Baer 5 months ago
parent
commit
8cf5d50560
9 changed files with 286 additions and 169 deletions
  1. 10
    15
      README.md
  2. 1
    3
      auth_test.go
  3. 51
    2
      collection.go
  4. 65
    21
      collection_test.go
  5. 1
    1
      go.mod
  6. 56
    28
      post.go
  7. 57
    80
      post_test.go
  8. 1
    3
      user.go
  9. 44
    16
      writeas.go

+ 10
- 15
README.md View File

@@ -1,13 +1,17 @@
1 1
 # go-writeas
2 2
 
3
-[![godoc](https://godoc.org/go.code.as/writeas.v1?status.svg)](https://godoc.org/go.code.as/writeas.v1)
3
+[![godoc](https://godoc.org/go.code.as/writeas.v2?status.svg)](https://godoc.org/go.code.as/writeas.v2)
4 4
 
5 5
 Official Write.as Go client library.
6 6
 
7 7
 ## Installation
8 8
 
9
+**Warning**: the `v2` branch is under heavy development and its API will change without notice.
10
+
11
+For a stable API, use `go.code.as/writeas.v1` and upgrade to `v2` once everything is merged into `master`.
12
+
9 13
 ```bash
10
-go get go.code.as/writeas.v1
14
+go get go.code.as/writeas.v2
11 15
 ```
12 16
 
13 17
 ## Documentation
@@ -17,7 +21,7 @@ See all functionality and usages in the [API documentation](https://developer.wr
17 21
 ### Example usage
18 22
 
19 23
 ```go
20
-import "go.code.as/writeas.v1"
24
+import "go.code.as/writeas.v2"
21 25
 
22 26
 func main() {
23 27
 	// Create the client
@@ -37,11 +41,7 @@ func main() {
37 41
 	token := p.Token
38 42
 
39 43
 	// Update a published post
40
-	p, err = c.UpdatePost(&writeas.PostParams{
41
-		OwnedPostParams: writeas.OwnedPostParams{
42
-			ID:    p.ID,
43
-			Token: token,
44
-		},
44
+	p, err = c.UpdatePost(p.ID, token, &writeas.PostParams{
45 45
 		Content: "Now it's been updated!",
46 46
 	})
47 47
 	if err != nil {
@@ -55,12 +55,7 @@ func main() {
55 55
 	}
56 56
 
57 57
 	// Delete a post
58
-	err = c.DeletePost(&writeas.PostParams{
59
-		OwnedPostParams: writeas.OwnedPostParams{
60
-			ID:    p.ID,
61
-			Token: token,
62
-		},
63
-	})
58
+	err = c.DeletePost(p.ID, token)
64 59
 }
65 60
 ```
66 61
 
@@ -68,7 +63,7 @@ func main() {
68 63
 
69 64
 The library covers our usage, but might not be comprehensive of the API. So we always welcome contributions and improvements from the community. Before sending pull requests, make sure you've done the following:
70 65
 
71
-* Run `go fmt` on all updated .go files.
66
+* Run `goimports` on all updated .go files.
72 67
 * Document all exported structs and funcs.
73 68
 
74 69
 ## License

+ 1
- 3
auth_test.go View File

@@ -1,8 +1,6 @@
1 1
 package writeas
2 2
 
3
-import (
4
-	"testing"
5
-)
3
+import "testing"
6 4
 
7 5
 func TestAuthentication(t *testing.T) {
8 6
 	dwac := NewDevClient()

+ 51
- 2
collection.go View File

@@ -26,8 +26,9 @@ type (
26 26
 
27 27
 	// CollectionParams holds values for creating a collection.
28 28
 	CollectionParams struct {
29
-		Alias string `json:"alias"`
30
-		Title string `json:"title"`
29
+		Alias       string `json:"alias"`
30
+		Title       string `json:"title"`
31
+		Description string `json:"description,omitempty"`
31 32
 	}
32 33
 )
33 34
 
@@ -112,6 +113,30 @@ func (c *Client) GetCollectionPosts(alias string) (*[]Post, error) {
112 113
 	}
113 114
 }
114 115
 
116
+// GetCollectionPost retrieves a post from a collection
117
+// and any error (in user-friendly form) that occurs). See
118
+// https://developers.write.as/docs/api/#retrieve-a-collection-post
119
+func (c *Client) GetCollectionPost(alias, slug string) (*Post, error) {
120
+	post := Post{}
121
+
122
+	env, err := c.get(fmt.Sprintf("/collections/%s/posts/%s", alias, slug), &post)
123
+	if err != nil {
124
+		return nil, err
125
+	}
126
+
127
+	if _, ok := env.Data.(*Post); !ok {
128
+		return nil, fmt.Errorf("Wrong data returned from API.")
129
+	}
130
+
131
+	if env.Code == http.StatusOK {
132
+		return &post, nil
133
+	} else if env.Code == http.StatusNotFound {
134
+		return nil, fmt.Errorf("Post %s not found in collection %s", slug, alias)
135
+	}
136
+
137
+	return nil, fmt.Errorf("Problem getting post %s from collection %s: %d. %v\n", slug, alias, env.Code, err)
138
+}
139
+
115 140
 // GetUserCollections retrieves the authenticated user's collections.
116 141
 // See https://developers.write.as/docs/api/#retrieve-user-39-s-collections
117 142
 func (c *Client) GetUserCollections() (*[]Collection, error) {
@@ -135,3 +160,27 @@ func (c *Client) GetUserCollections() (*[]Collection, error) {
135 160
 	}
136 161
 	return colls, nil
137 162
 }
163
+
164
+// DeleteCollection permanently deletes a collection and makes any posts on it
165
+// anonymous.
166
+//
167
+// See https://developers.write.as/docs/api/#delete-a-collection.
168
+func (c *Client) DeleteCollection(alias string) error {
169
+	endpoint := "/collections/" + alias
170
+	env, err := c.delete(endpoint, nil /* data */)
171
+	if err != nil {
172
+		return err
173
+	}
174
+
175
+	status := env.Code
176
+	switch status {
177
+	case http.StatusNoContent:
178
+		return nil
179
+	case http.StatusUnauthorized:
180
+		return fmt.Errorf("Not authenticated.")
181
+	case http.StatusBadRequest:
182
+		return fmt.Errorf("Bad request: %s", env.ErrorMessage)
183
+	default:
184
+		return fmt.Errorf("Problem deleting collection: %d. %s\n", status, env.ErrorMessage)
185
+	}
186
+}

+ 65
- 21
collection_test.go View File

@@ -2,34 +2,51 @@ package writeas
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"strings"
5 6
 	"testing"
7
+	"time"
6 8
 )
7 9
 
8 10
 func TestGetCollection(t *testing.T) {
9
-	wac := NewClient()
11
+	dwac := NewDevClient()
10 12
 
11
-	res, err := wac.GetCollection("blog")
13
+	res, err := dwac.GetCollection("tester")
12 14
 	if err != nil {
13 15
 		t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
14
-	} else {
15
-		t.Logf("Collection: %+v", res)
16
-		if res.Title != "write.as" {
17
-			t.Errorf("Unexpected fetch results: %+v\n", res)
18
-		}
16
+	}
17
+	if res == nil {
18
+		t.Error("Expected collection to not be nil")
19 19
 	}
20 20
 }
21 21
 
22 22
 func TestGetCollectionPosts(t *testing.T) {
23
-	wac := NewClient()
23
+	dwac := NewDevClient()
24
+	posts := []Post{}
24 25
 
25
-	res, err := wac.GetCollectionPosts("blog")
26
-	if err != nil {
27
-		t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
28
-	} else {
26
+	t.Run("Get all posts in collection", func(t *testing.T) {
27
+		res, err := dwac.GetCollectionPosts("tester")
28
+		if err != nil {
29
+			t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
30
+		}
29 31
 		if len(*res) == 0 {
30
-			t.Errorf("No posts returned!")
32
+			t.Error("Expected at least on post in collection")
31 33
 		}
32
-	}
34
+		posts = *res
35
+	})
36
+	t.Run("Get one post from collection", func(t *testing.T) {
37
+		res, err := dwac.GetCollectionPost("tester", posts[0].Slug)
38
+		if err != nil {
39
+			t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
40
+		}
41
+
42
+		if res == nil {
43
+			t.Errorf("No post returned!")
44
+		}
45
+
46
+		if len(res.Content) == 0 {
47
+			t.Errorf("Post content is empty!")
48
+		}
49
+	})
33 50
 }
34 51
 
35 52
 func TestGetUserCollections(t *testing.T) {
@@ -51,13 +68,40 @@ func TestGetUserCollections(t *testing.T) {
51 68
 	}
52 69
 }
53 70
 
54
-func ExampleClient_GetCollection() {
55
-	c := NewClient()
56
-	coll, err := c.GetCollection("blog")
71
+func TestCreateAndDeleteCollection(t *testing.T) {
72
+	wac := NewDevClient()
73
+	_, err := wac.LogIn("demo", "demo")
57 74
 	if err != nil {
58
-		fmt.Printf("%v", err)
59
-		return
75
+		t.Fatalf("Unable to log in: %v", err)
76
+	}
77
+	defer wac.LogOut()
78
+
79
+	now := time.Now().Unix()
80
+	alias := fmt.Sprintf("test-collection-%v", now)
81
+	c, err := wac.CreateCollection(&CollectionParams{
82
+		Alias: alias,
83
+		Title: fmt.Sprintf("Test Collection %v", now),
84
+	})
85
+	if err != nil {
86
+		t.Fatalf("Unable to create collection %q: %v", alias, err)
87
+	}
88
+
89
+	if err := wac.DeleteCollection(c.Alias); err != nil {
90
+		t.Fatalf("Unable to delete collection %q: %v", alias, err)
91
+	}
92
+}
93
+
94
+func TestDeleteCollectionUnauthenticated(t *testing.T) {
95
+	wac := NewDevClient()
96
+
97
+	now := time.Now().Unix()
98
+	alias := fmt.Sprintf("test-collection-does-not-exist-%v", now)
99
+	err := wac.DeleteCollection(alias)
100
+	if err == nil {
101
+		t.Fatalf("Should not be able to delete collection %q unauthenticated.", alias)
102
+	}
103
+
104
+	if !strings.Contains(err.Error(), "Not authenticated") {
105
+		t.Fatalf("Error message should be more informative: %v", err)
60 106
 	}
61
-	fmt.Printf("%s", coll.Title)
62
-	// Output: write.as
63 107
 }

+ 1
- 1
go.mod View File

@@ -1,4 +1,4 @@
1
-module github.com/writeas/go-writeas
1
+module github.com/writeas/go-writeas/v2
2 2
 
3 3
 go 1.9
4 4
 

+ 56
- 28
post.go View File

@@ -31,7 +31,7 @@ type (
31 31
 
32 32
 	// OwnedPostParams are, together, fields only the original post author knows.
33 33
 	OwnedPostParams struct {
34
-		ID    string `json:"-"`
34
+		ID    string `json:"id"`
35 35
 		Token string `json:"token,omitempty"`
36 36
 	}
37 37
 
@@ -42,11 +42,14 @@ type (
42 42
 		Token string `json:"token,omitempty"`
43 43
 
44 44
 		// Parameters for creating or updating
45
-		Title    string  `json:"title,omitempty"`
46
-		Content  string  `json:"body,omitempty"`
47
-		Font     string  `json:"font,omitempty"`
48
-		IsRTL    *bool   `json:"rtl,omitempty"`
49
-		Language *string `json:"lang,omitempty"`
45
+		Slug     string     `json:"slug"`
46
+		Created  *time.Time `json:"created,omitempty"`
47
+		Updated  *time.Time `json:"updated,omitempty"`
48
+		Title    string     `json:"title,omitempty"`
49
+		Content  string     `json:"body,omitempty"`
50
+		Font     string     `json:"font,omitempty"`
51
+		IsRTL    *bool      `json:"rtl,omitempty"`
52
+		Language *string    `json:"lang,omitempty"`
50 53
 
51 54
 		// Parameters only for creating
52 55
 		Crosspost []map[string]string `json:"crosspost,omitempty"`
@@ -102,7 +105,7 @@ func (c *Client) GetPost(id string) (*Post, error) {
102 105
 	} else if status == http.StatusGone {
103 106
 		return nil, fmt.Errorf("Post unpublished.")
104 107
 	}
105
-	return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err)
108
+	return nil, fmt.Errorf("Problem getting post: %d. %s\n", status, env.ErrorMessage)
106 109
 }
107 110
 
108 111
 // CreatePost publishes a new post, returning a user-friendly error if one comes
@@ -124,20 +127,33 @@ func (c *Client) CreatePost(sp *PostParams) (*Post, error) {
124 127
 	}
125 128
 
126 129
 	status := env.Code
127
-	if status == http.StatusCreated {
128
-		return p, nil
129
-	} else if status == http.StatusBadRequest {
130
-		return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
131
-	} else {
132
-		return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err)
130
+	if status != http.StatusCreated {
131
+		if status == http.StatusBadRequest {
132
+			return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
133
+		}
134
+		return nil, fmt.Errorf("Problem creating post: %d. %s\n", status, env.ErrorMessage)
133 135
 	}
136
+	return p, nil
134 137
 }
135 138
 
136 139
 // UpdatePost updates a published post with the given PostParams. See
137 140
 // https://developer.write.as/docs/api/#update-a-post.
138
-func (c *Client) UpdatePost(sp *PostParams) (*Post, error) {
141
+func (c *Client) UpdatePost(id, token string, sp *PostParams) (*Post, error) {
142
+	return c.updatePost("", id, token, sp)
143
+}
144
+
145
+func (c *Client) updatePost(collection, identifier, token string, sp *PostParams) (*Post, error) {
139 146
 	p := &Post{}
140
-	env, err := c.put(fmt.Sprintf("/posts/%s", sp.ID), sp, p)
147
+	endpoint := "/posts/" + identifier
148
+	/*
149
+		if collection != "" {
150
+			endpoint = "/collections/" + collection + endpoint
151
+		} else {
152
+			sp.Token = token
153
+		}
154
+	*/
155
+	sp.Token = token
156
+	env, err := c.put(endpoint, sp, p)
141 157
 	if err != nil {
142 158
 		return nil, err
143 159
 	}
@@ -154,17 +170,29 @@ func (c *Client) UpdatePost(sp *PostParams) (*Post, error) {
154 170
 		} else if status == http.StatusBadRequest {
155 171
 			return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
156 172
 		}
157
-		return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err)
173
+		return nil, fmt.Errorf("Problem updating post: %d. %s\n", status, env.ErrorMessage)
158 174
 	}
159 175
 	return p, nil
160 176
 }
161 177
 
162 178
 // DeletePost permanently deletes a published post. See
163 179
 // https://developer.write.as/docs/api/#delete-a-post.
164
-func (c *Client) DeletePost(sp *PostParams) error {
165
-	env, err := c.delete(fmt.Sprintf("/posts/%s", sp.ID), map[string]string{
166
-		"token": sp.Token,
167
-	})
180
+func (c *Client) DeletePost(id, token string) error {
181
+	return c.deletePost("", id, token)
182
+}
183
+
184
+func (c *Client) deletePost(collection, identifier, token string) error {
185
+	p := map[string]string{}
186
+	endpoint := "/posts/" + identifier
187
+	/*
188
+		if collection != "" {
189
+			endpoint = "/collections/" + collection + endpoint
190
+		} else {
191
+			p["token"] = token
192
+		}
193
+	*/
194
+	p["token"] = token
195
+	env, err := c.delete(endpoint, p)
168 196
 	if err != nil {
169 197
 		return err
170 198
 	}
@@ -177,14 +205,14 @@ func (c *Client) DeletePost(sp *PostParams) error {
177 205
 	} else if status == http.StatusBadRequest {
178 206
 		return fmt.Errorf("Bad request: %s", env.ErrorMessage)
179 207
 	}
180
-	return fmt.Errorf("Problem getting post: %d. %v\n", status, err)
208
+	return fmt.Errorf("Problem deleting post: %d. %s\n", status, env.ErrorMessage)
181 209
 }
182 210
 
183 211
 // ClaimPosts associates anonymous posts with a user / account.
184 212
 // https://developer.write.as/docs/api/#claim-posts.
185 213
 func (c *Client) ClaimPosts(sp *[]OwnedPostParams) (*[]ClaimPostResult, error) {
186 214
 	p := &[]ClaimPostResult{}
187
-	env, err := c.put("/posts/claim", sp, p)
215
+	env, err := c.post("/posts/claim", sp, p)
188 216
 	if err != nil {
189 217
 		return nil, err
190 218
 	}
@@ -202,7 +230,7 @@ func (c *Client) ClaimPosts(sp *[]OwnedPostParams) (*[]ClaimPostResult, error) {
202 230
 	} else if status == http.StatusBadRequest {
203 231
 		return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
204 232
 	} else {
205
-		return nil, fmt.Errorf("Problem getting post: %d. %v\n", status, err)
233
+		return nil, fmt.Errorf("Problem claiming post: %d. %s\n", status, env.ErrorMessage)
206 234
 	}
207 235
 	// TODO: does this also happen with moving posts?
208 236
 }
@@ -226,7 +254,7 @@ func (c *Client) GetUserPosts() (*[]Post, error) {
226 254
 		if c.isNotLoggedIn(status) {
227 255
 			return nil, fmt.Errorf("Not authenticated.")
228 256
 		}
229
-		return nil, fmt.Errorf("Problem getting posts: %d. %v\n", status, err)
257
+		return nil, fmt.Errorf("Problem getting user posts: %d. %s\n", status, env.ErrorMessage)
230 258
 	}
231 259
 	return p, nil
232 260
 }
@@ -251,7 +279,7 @@ func (c *Client) PinPost(alias string, pp *PinnedPostParams) error {
251 279
 		if c.isNotLoggedIn(status) {
252 280
 			return fmt.Errorf("Not authenticated.")
253 281
 		}
254
-		return fmt.Errorf("Problem pinning post: %d. %v\n", status, err)
282
+		return fmt.Errorf("Problem pinning post: %d. %s\n", status, env.ErrorMessage)
255 283
 	}
256 284
 
257 285
 	// Check the individual post result
@@ -261,7 +289,7 @@ func (c *Client) PinPost(alias string, pp *PinnedPostParams) error {
261 289
 	if (*res)[0].Code != http.StatusOK {
262 290
 		return fmt.Errorf("Problem pinning post: %d", (*res)[0].Code)
263 291
 		// TODO: return ErrorMessage (right now it'll be empty)
264
-		// return fmt.Errorf("Problem pinning post: %v", res[0].ErrorMessage)
292
+		// return fmt.Errorf("Problem pinning post: %s", res[0].ErrorMessage)
265 293
 	}
266 294
 	return nil
267 295
 }
@@ -286,7 +314,7 @@ func (c *Client) UnpinPost(alias string, pp *PinnedPostParams) error {
286 314
 		if c.isNotLoggedIn(status) {
287 315
 			return fmt.Errorf("Not authenticated.")
288 316
 		}
289
-		return fmt.Errorf("Problem unpinning post: %d. %v\n", status, err)
317
+		return fmt.Errorf("Problem unpinning post: %d. %s\n", status, env.ErrorMessage)
290 318
 	}
291 319
 
292 320
 	// Check the individual post result
@@ -296,7 +324,7 @@ func (c *Client) UnpinPost(alias string, pp *PinnedPostParams) error {
296 324
 	if (*res)[0].Code != http.StatusOK {
297 325
 		return fmt.Errorf("Problem unpinning post: %d", (*res)[0].Code)
298 326
 		// TODO: return ErrorMessage (right now it'll be empty)
299
-		// return fmt.Errorf("Problem unpinning post: %v", res[0].ErrorMessage)
327
+		// return fmt.Errorf("Problem unpinning post: %s", res[0].ErrorMessage)
300 328
 	}
301 329
 	return nil
302 330
 }

+ 57
- 80
post_test.go View File

@@ -1,89 +1,58 @@
1 1
 package writeas
2 2
 
3 3
 import (
4
-	"testing"
5
-
6 4
 	"fmt"
7
-	"strings"
5
+	"testing"
8 6
 )
9 7
 
10
-func TestCreatePost(t *testing.T) {
11
-	wac := NewClient()
12
-	p, err := wac.CreatePost(&PostParams{
13
-		Title:   "Title!",
14
-		Content: "This is a post.",
15
-		Font:    "sans",
16
-	})
17
-	if err != nil {
18
-		t.Errorf("Post create failed: %v", err)
19
-		return
20
-	}
21
-	t.Logf("Post created: %+v", p)
22
-
23
-	token := p.Token
24
-
25
-	// Update post
26
-	p, err = wac.UpdatePost(&PostParams{
27
-		ID:      p.ID,
28
-		Token:   token,
29
-		Content: "Now it's been updated!",
8
+func TestPostRoundTrip(t *testing.T) {
9
+	var id, token string
10
+	dwac := NewClient()
11
+	t.Run("Create post", func(t *testing.T) {
12
+		p, err := dwac.CreatePost(&PostParams{
13
+			Title:   "Title!",
14
+			Content: "This is a post.",
15
+			Font:    "sans",
16
+		})
17
+		if err != nil {
18
+			t.Errorf("Post create failed: %v", err)
19
+			return
20
+		}
21
+		t.Logf("Post created: %+v", p)
22
+		id, token = p.ID, p.Token
30 23
 	})
31
-	if err != nil {
32
-		t.Errorf("Post update failed: %v", err)
33
-		return
34
-	}
35
-	t.Logf("Post updated: %+v", p)
36
-
37
-	// Delete post
38
-	err = wac.DeletePost(&PostParams{
39
-		ID:    p.ID,
40
-		Token: token,
24
+	t.Run("Get post", func(t *testing.T) {
25
+		res, err := dwac.GetPost(id)
26
+		if err != nil {
27
+			t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
28
+		} else {
29
+			t.Logf("Post: %+v", res)
30
+			if res.Content != "This is a post." {
31
+				t.Errorf("Unexpected fetch results: %+v\n", res)
32
+			}
33
+		}
41 34
 	})
42
-	if err != nil {
43
-		t.Errorf("Post delete failed: %v", err)
44
-		return
45
-	}
46
-	t.Logf("Post deleted!")
47
-}
48
-
49
-func TestGetPost(t *testing.T) {
50
-	dwac := NewDevClient()
51
-	res, err := dwac.GetPost("zekk5r9apum6p")
52
-	if err != nil {
53
-		t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
54
-	} else {
55
-		t.Logf("Post: %+v", res)
56
-		if res.Content != "This is a post." {
57
-			t.Errorf("Unexpected fetch results: %+v\n", res)
35
+	t.Run("Update post", func(t *testing.T) {
36
+		p, err := dwac.UpdatePost(id, token, &PostParams{
37
+			Content: "Now it's been updated!",
38
+		})
39
+		if err != nil {
40
+			t.Errorf("Post update failed: %v", err)
41
+			return
58 42
 		}
59
-	}
60
-
61
-	wac := NewClient()
62
-	res, err = wac.GetPost("3psnxyhqxy3hq")
63
-	if err != nil {
64
-		t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err)
65
-	} else {
66
-		if !strings.HasPrefix(res.Content, "                               Write.as Blog") {
67
-			t.Errorf("Unexpected fetch results: %+v\n", res)
43
+		t.Logf("Post updated: %+v", p)
44
+	})
45
+	t.Run("Delete post", func(t *testing.T) {
46
+		err := dwac.DeletePost(id, token)
47
+		if err != nil {
48
+			t.Errorf("Post delete failed: %v", err)
49
+			return
68 50
 		}
69
-	}
70
-}
71
-
72
-func TestPinPost(t *testing.T) {
73
-	dwac := NewDevClient()
74
-	_, err := dwac.LogIn("demo", "demo")
75
-	if err != nil {
76
-		t.Fatalf("Unable to log in: %v", err)
77
-	}
78
-	defer dwac.LogOut()
79
-
80
-	err = dwac.PinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"})
81
-	if err != nil {
82
-		t.Fatalf("Pin failed: %v", err)
83
-	}
51
+		t.Logf("Post deleted!")
52
+	})
84 53
 }
85 54
 
86
-func TestUnpinPost(t *testing.T) {
55
+func TestPinUnPin(t *testing.T) {
87 56
 	dwac := NewDevClient()
88 57
 	_, err := dwac.LogIn("demo", "demo")
89 58
 	if err != nil {
@@ -91,17 +60,25 @@ func TestUnpinPost(t *testing.T) {
91 60
 	}
92 61
 	defer dwac.LogOut()
93 62
 
94
-	err = dwac.UnpinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"})
95
-	if err != nil {
96
-		t.Fatalf("Unpin failed: %v", err)
97
-	}
63
+	t.Run("Pin post", func(t *testing.T) {
64
+		err := dwac.PinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"})
65
+		if err != nil {
66
+			t.Fatalf("Pin failed: %v", err)
67
+		}
68
+	})
69
+	t.Run("Unpin post", func(t *testing.T) {
70
+		err := dwac.UnpinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"})
71
+		if err != nil {
72
+			t.Fatalf("Unpin failed: %v", err)
73
+		}
74
+	})
98 75
 }
99 76
 
100 77
 func ExampleClient_CreatePost() {
101
-	c := NewClient()
78
+	dwac := NewDevClient()
102 79
 
103 80
 	// Publish a post
104
-	p, err := c.CreatePost(&PostParams{
81
+	p, err := dwac.CreatePost(&PostParams{
105 82
 		Title:   "Title!",
106 83
 		Content: "This is a post.",
107 84
 		Font:    "sans",

+ 1
- 3
user.go View File

@@ -1,8 +1,6 @@
1 1
 package writeas
2 2
 
3
-import (
4
-	"time"
5
-)
3
+import "time"
6 4
 
7 5
 type (
8 6
 	// AuthUser represents a just-authenticated user. It contains information

+ 44
- 16
writeas.go View File

@@ -3,19 +3,23 @@ package writeas
3 3
 
4 4
 import (
5 5
 	"bytes"
6
-	"code.as/core/socks"
7 6
 	"encoding/json"
8 7
 	"fmt"
9
-	"github.com/writeas/impart"
10 8
 	"io"
11 9
 	"net/http"
12 10
 	"time"
11
+
12
+	"code.as/core/socks"
13
+	"github.com/writeas/impart"
13 14
 )
14 15
 
15 16
 const (
16 17
 	apiURL    = "https://write.as/api"
17 18
 	devAPIURL = "https://development.write.as/api"
18 19
 	torAPIURL = "http://writeas7pm7rcdqg.onion/api"
20
+
21
+	// Current go-writeas version
22
+	Version = "2-dev"
19 23
 )
20 24
 
21 25
 // Client is used to interact with the Write.as API. It can be used to make
@@ -41,31 +45,55 @@ const defaultHTTPTimeout = 10 * time.Second
41 45
 //     c := writeas.NewClient()
42 46
 //     c.SetToken("00000000-0000-0000-0000-000000000000")
43 47
 func NewClient() *Client {
44
-	return &Client{
45
-		client:  &http.Client{Timeout: defaultHTTPTimeout},
46
-		baseURL: apiURL,
47
-	}
48
+	return NewClientWith(Config{URL: apiURL})
48 49
 }
49 50
 
50 51
 // NewTorClient creates a new API client for communicating with the Write.as
51 52
 // Tor hidden service, using the given port to connect to the local SOCKS
52 53
 // proxy.
53 54
 func NewTorClient(port int) *Client {
54
-	dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, fmt.Sprintf("127.0.0.1:%d", port))
55
-	transport := &http.Transport{Dial: dialSocksProxy}
56
-	return &Client{
57
-		client:  &http.Client{Transport: transport},
58
-		baseURL: torAPIURL,
59
-	}
55
+	return NewClientWith(Config{URL: torAPIURL, TorPort: port})
60 56
 }
61 57
 
62 58
 // NewDevClient creates a new API client for development and testing. It'll
63 59
 // communicate with our development servers, and SHOULD NOT be used in
64 60
 // production.
65 61
 func NewDevClient() *Client {
62
+	return NewClientWith(Config{URL: devAPIURL})
63
+}
64
+
65
+// Config configures a Write.as client.
66
+type Config struct {
67
+	// URL of the Write.as API service. Defaults to https://write.as/api.
68
+	URL string
69
+
70
+	// If specified, the API client will communicate with the Write.as Tor
71
+	// hidden service using the provided port to connect to the local SOCKS
72
+	// proxy.
73
+	TorPort int
74
+
75
+	// If specified, requests will be authenticated using this user token.
76
+	// This may be provided after making a few anonymous requests with
77
+	// SetToken.
78
+	Token string
79
+}
80
+
81
+// NewClientWith builds a new API client with the provided configuration.
82
+func NewClientWith(c Config) *Client {
83
+	if c.URL == "" {
84
+		c.URL = apiURL
85
+	}
86
+
87
+	httpClient := &http.Client{Timeout: defaultHTTPTimeout}
88
+	if c.TorPort > 0 {
89
+		dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, fmt.Sprintf("127.0.0.1:%d", c.TorPort))
90
+		httpClient.Transport = &http.Transport{Dial: dialSocksProxy}
91
+	}
92
+
66 93
 	return &Client{
67
-		client:  &http.Client{Timeout: defaultHTTPTimeout},
68
-		baseURL: devAPIURL,
94
+		client:  httpClient,
95
+		baseURL: c.URL,
96
+		token:   c.Token,
69 97
 	}
70 98
 }
71 99
 
@@ -161,9 +189,9 @@ func (c *Client) doRequest(r *http.Request, result interface{}) (*impart.Envelop
161 189
 func (c *Client) prepareRequest(r *http.Request) {
162 190
 	ua := c.UserAgent
163 191
 	if ua == "" {
164
-		ua = "go-writeas v1"
192
+		ua = "go-writeas v" + Version
165 193
 	}
166
-	r.Header.Add("User-Agent", ua)
194
+	r.Header.Set("User-Agent", ua)
167 195
 	r.Header.Add("Content-Type", "application/json")
168 196
 	if c.token != "" {
169 197
 		r.Header.Add("Authorization", "Token "+c.token)