diff --git a/auth_test.go b/auth_test.go index a9ece6f..6706db5 100644 --- a/auth_test.go +++ b/auth_test.go @@ -1,19 +1,19 @@ package writeas import "testing" func TestAuthentication(t *testing.T) { - dwac := NewDevClient() + dwac := NewClient(devAPIURL) // Log in _, err := dwac.LogIn("demo", "demo") if err != nil { t.Fatalf("Unable to log in: %v", err) } // Log out err = dwac.LogOut() if err != nil { t.Fatalf("Unable to log out: %v", err) } } diff --git a/collection_test.go b/collection_test.go index d6bb49b..f3356b6 100644 --- a/collection_test.go +++ b/collection_test.go @@ -1,107 +1,107 @@ package writeas import ( "fmt" "strings" "testing" "time" ) func TestGetCollection(t *testing.T) { - dwac := NewDevClient() + dwac := NewClient(devAPIURL) res, err := dwac.GetCollection("tester") if err != nil { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } if res == nil { t.Error("Expected collection to not be nil") } } func TestGetCollectionPosts(t *testing.T) { - dwac := NewDevClient() + dwac := NewClient(devAPIURL) posts := []Post{} t.Run("Get all posts in collection", func(t *testing.T) { res, err := dwac.GetCollectionPosts("tester") if err != nil { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } if len(*res) == 0 { t.Error("Expected at least on post in collection") } posts = *res }) t.Run("Get one post from collection", func(t *testing.T) { res, err := dwac.GetCollectionPost("tester", posts[0].Slug) if err != nil { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } 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() + wac := NewClient(devAPIURL) _, err := wac.LogIn("demo", "demo") if err != nil { t.Fatalf("Unable to log in: %v", err) } defer wac.LogOut() res, err := wac.GetUserCollections() if err != nil { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } else { t.Logf("User collections: %+v", res) if len(*res) == 0 { t.Errorf("No collections returned!") } } } func TestCreateAndDeleteCollection(t *testing.T) { - wac := NewDevClient() + wac := NewClient(devAPIURL) _, err := wac.LogIn("demo", "demo") if err != nil { t.Fatalf("Unable to log in: %v", err) } defer wac.LogOut() now := time.Now().Unix() alias := fmt.Sprintf("test-collection-%v", now) c, err := wac.CreateCollection(&CollectionParams{ Alias: alias, Title: fmt.Sprintf("Test Collection %v", now), }) if err != nil { t.Fatalf("Unable to create collection %q: %v", alias, err) } if err := wac.DeleteCollection(c.Alias); err != nil { t.Fatalf("Unable to delete collection %q: %v", alias, err) } } func TestDeleteCollectionUnauthenticated(t *testing.T) { - wac := NewDevClient() + wac := NewClient(devAPIURL) now := time.Now().Unix() alias := fmt.Sprintf("test-collection-does-not-exist-%v", now) err := wac.DeleteCollection(alias) if err == nil { t.Fatalf("Should not be able to delete collection %q unauthenticated.", alias) } if !strings.Contains(err.Error(), "Not authenticated") { t.Fatalf("Error message should be more informative: %v", err) } } diff --git a/post_test.go b/post_test.go index 9d2fb47..304dd45 100644 --- a/post_test.go +++ b/post_test.go @@ -1,93 +1,93 @@ package writeas import ( "fmt" "testing" ) func TestPostRoundTrip(t *testing.T) { var id, token string - dwac := NewClient() + dwac := NewClient(devAPIURL) t.Run("Create post", func(t *testing.T) { p, err := dwac.CreatePost(&PostParams{ Title: "Title!", Content: "This is a post.", Font: "sans", }) if err != nil { t.Errorf("Post create failed: %v", err) return } t.Logf("Post created: %+v", p) id, token = p.ID, p.Token }) t.Run("Get post", func(t *testing.T) { res, err := dwac.GetPost(id) if err != nil { t.Errorf("Unexpected fetch results: %+v, err: %v\n", res, err) } else { t.Logf("Post: %+v", res) if res.Content != "This is a post." { t.Errorf("Unexpected fetch results: %+v\n", res) } } }) t.Run("Update post", func(t *testing.T) { p, err := dwac.UpdatePost(id, token, &PostParams{ Content: "Now it's been updated!", }) if err != nil { t.Errorf("Post update failed: %v", err) return } t.Logf("Post updated: %+v", p) }) t.Run("Delete post", func(t *testing.T) { err := dwac.DeletePost(id, token) if err != nil { t.Errorf("Post delete failed: %v", err) return } t.Logf("Post deleted!") }) } func TestPinUnPin(t *testing.T) { - dwac := NewDevClient() + dwac := NewClient(devAPIURL) _, err := dwac.LogIn("demo", "demo") if err != nil { t.Fatalf("Unable to log in: %v", err) } defer dwac.LogOut() t.Run("Pin post", func(t *testing.T) { err := dwac.PinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"}) if err != nil { t.Fatalf("Pin failed: %v", err) } }) t.Run("Unpin post", func(t *testing.T) { err := dwac.UnpinPost("tester", &PinnedPostParams{ID: "olx6uk7064heqltf"}) if err != nil { t.Fatalf("Unpin failed: %v", err) } }) } func ExampleClient_CreatePost() { - dwac := NewDevClient() + dwac := NewClient(devAPIURL) // Publish a post p, err := dwac.CreatePost(&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/writeas.go b/writeas.go index fa87ae1..3c15de9 100644 --- a/writeas.go +++ b/writeas.go @@ -1,199 +1,198 @@ // Package writeas provides the binding for the Write.as API package writeas import ( "bytes" "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://writeas7pm7rcdqg.onion/api" // Current go-writeas version Version = "2-dev" ) // Client is used to interact with the Write.as API. It can be used to make // authenticated or unauthenticated calls. type Client struct { baseURL string // Access token for the user making requests. token 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") -func NewClient() *Client { - return NewClientWith(Config{URL: apiURL}) +func NewClient(baseURL string) *Client { + if baseURL == "" { + baseURL = apiURL + } + return NewClientWith(Config{URL: baseURL}) } // 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}) +func NewTorClient(baseURL string, port int) *Client { + if baseURL == "" { + baseURL = torAPIURL + } + return NewClientWith(Config{URL: baseURL, TorPort: port}) } // 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 } // Token returns the user token currently set to the Client. func (c *Client) Token() string { return c.token } func (c *Client) get(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) } func (c *Client) post(path string, data, r interface{}) (*impart.Envelope, error) { b := new(bytes.Buffer) json.NewEncoder(b).Encode(data) return c.request("POST", path, b, r) } func (c *Client) put(path string, data, r interface{}) (*impart.Envelope, error) { b := new(bytes.Buffer) json.NewEncoder(b).Encode(data) return c.request("PUT", path, b, r) } func (c *Client) delete(path string, data map[string]string) (*impart.Envelope, error) { r, err := c.buildRequest("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) if err != nil { return nil, err } return c.doRequest(r, result) } func (c *Client) buildRequest(method, path string, data io.Reader) (*http.Request, error) { url := fmt.Sprintf("%s%s", c.baseURL, path) r, err := http.NewRequest(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) } }