From 6bda111ffa77c55bef564324eee507f2a14552bb Mon Sep 17 00:00:00 2001 From: Andrey Beletsky Date: Thu, 9 May 2019 19:54:44 +0700 Subject: [PATCH] Add internal-apis client --- extras/lbryinc/client.go | 112 ++++++++++++++++++++++++++++++++++ extras/lbryinc/client_test.go | 65 ++++++++++++++++++++ go.mod | 2 +- 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 extras/lbryinc/client.go create mode 100644 extras/lbryinc/client_test.go diff --git a/extras/lbryinc/client.go b/extras/lbryinc/client.go new file mode 100644 index 0000000..e33f32b --- /dev/null +++ b/extras/lbryinc/client.go @@ -0,0 +1,112 @@ +package lbryinc + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "time" + + "github.com/sirupsen/logrus" +) + +// Client stores data about internal-apis call it is about to make. +type Client struct { + ServerAddress string + AuthToken string + Logger *logrus.Logger +} + +// APIResponse reflects internal-apis JSON response format. +type APIResponse struct { + Success bool `json:"success"` + Error *string `json:"error"` + Data *ResponseData `json:"data"` +} + +// ResponseData is a map containing parsed json response. +type ResponseData map[string]interface{} + +const defaultServerAddress = "https://api.lbry.com" + +// const defaultServerAddress = "http://127.0.0.1:9000/" +const timeout = 5 * time.Second + +const userObjectPath = "user" + +// NewClient returns a client instance for internal-apis. It requires authToken to be provided +// for authentication. +func NewClient(authToken string) Client { + return Client{ + ServerAddress: defaultServerAddress, + AuthToken: authToken, + Logger: logrus.StandardLogger(), + } +} + +func (c Client) getEndpointURL(object, method string) string { + return fmt.Sprintf("%s/%s/%s", c.ServerAddress, object, method) +} + +func (c Client) prepareParams(params map[string]interface{}) (string, error) { + form := url.Values{} + form.Add("auth_token", c.AuthToken) + for k, v := range params { + if k == "auth_token" { + return "", errors.New("extra auth_token supplied in request params") + } + form.Add(k, fmt.Sprintf("%v", v)) + } + return form.Encode(), nil +} + +func (c Client) doCall(url string, payload string) (body []byte, err error) { + c.Logger.Debugf("sending payload: %s", payload) + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer([]byte(payload))) + if err != nil { + return body, err + } + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{Timeout: timeout} + r, err := client.Do(req) + if err != nil { + return body, err + } + defer r.Body.Close() + + body, err = ioutil.ReadAll(r.Body) + return body, err +} + +// Call calls a remote internal-apis server, returning a response, +// wrapped into standardized API Response struct. +func (c Client) Call(object, method string, params map[string]interface{}) (rd ResponseData, err error) { + payload, err := c.prepareParams(params) + if err != nil { + return rd, err + } + + body, err := c.doCall(c.getEndpointURL(object, method), payload) + if err != nil { + return rd, err + } + var ar APIResponse + err = json.Unmarshal(body, &ar) + if err != nil { + return rd, err + } + if !ar.Success { + return rd, errors.New(*ar.Error) + } + return *ar.Data, err +} + +// UserMe returns user details for the user associated with the current auth_token +func (c Client) UserMe() (ResponseData, error) { + return c.Call(userObjectPath, "me", map[string]interface{}{}) +} diff --git a/extras/lbryinc/client_test.go b/extras/lbryinc/client_test.go new file mode 100644 index 0000000..1e1b823 --- /dev/null +++ b/extras/lbryinc/client_test.go @@ -0,0 +1,65 @@ +package lbryinc + +import ( + "log" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUserMeWrongToken(t *testing.T) { + c := NewClient("abc") + r, err := c.UserMe() + require.NotNil(t, err) + assert.Equal(t, "could not authenticate user", err.Error()) + assert.Nil(t, r) +} + +const dummyServerURL = "http://127.0.0.1:59999" + +func launchDummyServer() { + s := &http.Server{ + Addr: "127.0.0.1:59999", + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + response := []byte(`{ + "success": true, + "error": null, + "data": { + "id": 751365, + "language": "en", + "given_name": null, + "family_name": null, + "created_at": "2019-01-17T12:13:06Z", + "updated_at": "2019-05-02T13:57:59Z", + "invited_by_id": null, + "invited_at": null, + "invites_remaining": 0, + "invite_reward_claimed": false, + "is_email_enabled": true, + "manual_approval_user_id": 837139, + "reward_status_change_trigger": "manual", + "primary_email": "andrey@lbry.com", + "has_verified_email": true, + "is_identity_verified": false, + "is_reward_approved": true, + "groups": [] + } + }`) + w.Write(response) + }), + } + log.Fatal(s.ListenAndServe()) +} + +func TestUserMe(t *testing.T) { + go launchDummyServer() + c := NewClient("realToken") + c.ServerAddress = dummyServerURL + r, err := c.UserMe() + assert.Nil(t, err) + assert.Equal(t, r["primary_email"], "andrey@lbry.com") +} diff --git a/go.mod b/go.mod index 69cb2c9..866a8b5 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect github.com/spf13/cast v1.2.0 - github.com/stretchr/testify v1.3.0 // indirect + github.com/stretchr/testify v1.3.0 github.com/uber-go/atomic v1.3.2 github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d go.uber.org/atomic v1.3.2 // indirect