From afbbcf4e573d8a1868000b8f9088b5ec1780ae8a Mon Sep 17 00:00:00 2001 From: Oleg Lobanov Date: Mon, 11 Dec 2017 13:48:36 +0300 Subject: [PATCH 01/46] fix media compatibility --- .gitignore | 2 ++ client.go | 11 ++++++++--- media.go | 4 ++-- posts.go | 13 ++++++++++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 2559738..b57fd99 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ _testmain.go *.test *.prof credential.txt + +.idea diff --git a/client.go b/client.go index e1163ad..4243a74 100644 --- a/client.go +++ b/client.go @@ -6,6 +6,7 @@ import ( "github.com/parnurzeal/gorequest" "io/ioutil" "log" + "mime/multipart" "net/http" "reflect" ) @@ -193,14 +194,18 @@ func (client *Client) PostData(url string, content []byte, contentType string, f // so, we have to manually create a HTTP client s := client.req.Post(url) - buf := bytes.NewBuffer(content) + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + fileField, _ := w.CreateFormFile("file", filename) + fileField.Write(content) + w.Close() - req, err := http.NewRequest(s.Method, s.Url, buf) + req, err := http.NewRequest(s.Method, s.Url, &buf) if err != nil { return nil, nil, err } - req.Header.Set("Content-Type", contentType) + req.Header.Set("Content-Type", w.FormDataContentType()) req.Header.Set("Content-Disposition", fmt.Sprintf("filename=%v", filename)) // Add basic auth diff --git a/media.go b/media.go index 749da48..802104b 100644 --- a/media.go +++ b/media.go @@ -49,8 +49,8 @@ type Media struct { MediaStatus string `json:"comment_status,omitempty"` PingStatus string `json:"ping_status,omitempty"` AltText string `json:"alt_text,omitempty"` - Caption string `json:"caption,omitempty"` - Description string `json:"description,omitempty"` + Caption Caption `json:"caption,omitempty"` + Description Description `json:"description,omitempty"` MediaType string `json:"media_type,omitempty"` MediaDetails MediaDetails `json:"media_details,omitempty"` Post int `json:"post,omitempty"` diff --git a/posts.go b/posts.go index e082b7a..3777bec 100644 --- a/posts.go +++ b/posts.go @@ -52,6 +52,16 @@ type Excerpt struct { Rendered string `json:"rendered,omitempty"` } +type Description struct { + Raw string `json:"raw,omitempty"` + Rendered string `json:"rendered,omitempty"` +} + +type Caption struct { + Raw string `json:"raw,omitempty"` + Rendered string `json:"rendered,omitempty"` +} + type Post struct { collection *PostsCollection `json:"-,omitempty"` @@ -59,6 +69,8 @@ type Post struct { Date string `json:"date,omitempty"` DateGMT string `json:"date_gmt,omitempty"` GUID GUID `json:"guid,omitempty"` + FeaturedMedia int `json:"featured_media"` + Categories []int `json:"categories"` Link string `json:"link,omitempty"` Modified string `json:"modified,omitempty"` ModifiedGMT string `json:"modifiedGMT,omitempty"` @@ -70,7 +82,6 @@ type Post struct { Content Content `json:"content,omitempty"` Author int `json:"author,omitempty"` Excerpt Excerpt `json:"excerpt,omitempty"` - FeaturedImage int `json:"featured_image,omitempty"` CommentStatus string `json:"comment_status,omitempty"` PingStatus string `json:"ping_status,omitempty"` Format string `json:"format,omitempty"` From 34228a9f1d32cf6487e141762d11b651fd92c021 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 25 Dec 2017 14:20:04 -0500 Subject: [PATCH 02/46] Add full media size --- media.go | 1 + 1 file changed, 1 insertion(+) diff --git a/media.go b/media.go index 802104b..6fcd807 100644 --- a/media.go +++ b/media.go @@ -17,6 +17,7 @@ type MediaDetailsSizes struct { Medium MediaDetailsSizesItem `json:"medium,omitempty"` Large MediaDetailsSizesItem `json:"large,omitempty"` SiteLogo MediaDetailsSizesItem `json:"site-logo,omitempty"` + Full MediaDetailsSizesItem `json:"full,omitempty"` } type MediaDetails struct { Raw string `json:"raw,omitempty"` From 1ba8ba0af844b8f15851582308101bbf48e4138f Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 25 Dec 2017 15:00:24 -0500 Subject: [PATCH 03/46] Implement custom time formatting --- client.go | 3 ++- comments.go | 4 ++-- media.go | 8 ++++---- pages.go | 8 ++++---- posts.go | 8 ++++---- revisions.go | 8 ++++---- time.go | 24 ++++++++++++++++++++++++ 7 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 time.go diff --git a/client.go b/client.go index 4243a74..5abf072 100644 --- a/client.go +++ b/client.go @@ -3,12 +3,13 @@ package wordpress import ( "bytes" "fmt" - "github.com/parnurzeal/gorequest" "io/ioutil" "log" "mime/multipart" "net/http" "reflect" + + "github.com/parnurzeal/gorequest" ) const ( diff --git a/comments.go b/comments.go index 41050fc..c3b0d32 100644 --- a/comments.go +++ b/comments.go @@ -16,8 +16,8 @@ type Comment struct { AuthorURL string `json:"author_url,omitempty"` AuthorUserAgent string `json:"author_user_agent,omitempty"` Content Content `json:"content,omitempty"` - Date string `json:"date,omitempty"` - DateGMT string `json:"date_gmt,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` Karma int `json:"karma,omitempty"` Link string `json:"link,omitempty"` Parent int `json:"parent,omitempty"` diff --git a/media.go b/media.go index 6fcd807..4ca09fb 100644 --- a/media.go +++ b/media.go @@ -35,12 +35,12 @@ type MediaUploadOptions struct { } type Media struct { ID int `json:"id,omitempty"` - Date string `json:"date,omitempty"` - DateGMT string `json:"date_gmt,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` GUID GUID `json:"guid,omitempty"` Link string `json:"link,omitempty"` - Modified string `json:"modified,omitempty"` - ModifiedGMT string `json:"modifiedGMT,omitempty"` + Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modifiedGMT,omitempty"` Password string `json:"password,omitempty"` Slug string `json:"slug,omitempty"` Status string `json:"status,omitempty"` diff --git a/pages.go b/pages.go index 8852947..a844654 100644 --- a/pages.go +++ b/pages.go @@ -9,12 +9,12 @@ type Page struct { collection *PagesCollection `json:"-"` ID int `json:"id,omitempty"` - Date string `json:"date,omitempty"` - DateGMT string `json:"date_gmt,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` GUID GUID `json:"guid,omitempty"` Link string `json:"link,omitempty"` - Modified string `json:"modified,omitempty"` - ModifiedGMT string `json:"modifiedGMT,omitempty"` + Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modifiedGMT,omitempty"` Password string `json:"password,omitempty"` Slug string `json:"slug,omitempty"` Status string `json:"status,omitempty"` diff --git a/posts.go b/posts.go index 3777bec..01727e9 100644 --- a/posts.go +++ b/posts.go @@ -66,14 +66,14 @@ type Post struct { collection *PostsCollection `json:"-,omitempty"` ID int `json:"id,omitempty"` - Date string `json:"date,omitempty"` - DateGMT string `json:"date_gmt,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` GUID GUID `json:"guid,omitempty"` FeaturedMedia int `json:"featured_media"` Categories []int `json:"categories"` Link string `json:"link,omitempty"` - Modified string `json:"modified,omitempty"` - ModifiedGMT string `json:"modifiedGMT,omitempty"` + Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modifiedGMT,omitempty"` Password string `json:"password,omitempty"` Slug string `json:"slug,omitempty"` Status string `json:"status,omitempty"` diff --git a/revisions.go b/revisions.go index 4900335..f7a121c 100644 --- a/revisions.go +++ b/revisions.go @@ -8,11 +8,11 @@ import ( type Revision struct { ID int `json:"id,omitempty"` Author string `json:"author,omitempty"` // TODO: File a WP-API bug, why am I getting string instead of int? - Date string `json:"date,omitempty"` - DateGMT string `json:"dateGMT,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"dateGMT,omitempty"` GUID string `json:"guid,omitempty"` - Modified string `json:"modified,omitempty"` - ModifiedGMT string `json:"modifiedGMT,omitempty"` + Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modifiedGMT,omitempty"` Parent int `json:"parent,omitempty"` Slug string `json:"slug,omitempty"` Title string `json:"title,omitempty"` diff --git a/time.go b/time.go new file mode 100644 index 0000000..ff9d158 --- /dev/null +++ b/time.go @@ -0,0 +1,24 @@ +package wordpress + +import ( + "time" +) + +type Time struct { + time.Time +} + +// 2017-12-25T09:54:42 +const timeLayout = "2006-01-02T15:04:05" + +func (t *Time) UnmarshalJSON(b []byte) (err error) { + if b[0] == '"' && b[len(b)-1] == '"' { + b = b[1 : len(b)-1] + } + t.Time, err = time.Parse(timeLayout, string(b)) + return +} + +func (t *Time) MarshalJSON() ([]byte, error) { + return []byte(t.Time.Format(timeLayout)), nil +} From abb403306a6a9932670a4af12b9939ea328d49ad Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 25 Dec 2017 15:30:20 -0500 Subject: [PATCH 04/46] Add URL discovery functions --- discovery.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 discovery.go diff --git a/discovery.go b/discovery.go new file mode 100644 index 0000000..310553c --- /dev/null +++ b/discovery.go @@ -0,0 +1,64 @@ +package wordpress + +import ( + "net/http" + + "github.com/PuerkitoBio/goquery" + "github.com/tomnomnom/linkheader" +) + +type DiscoveredAPI struct { + BaseURL string + DiscoveredURL string + ViaHeader bool + ViaHTML bool +} + +// DiscoverAPI will discover the API root URL for the given base URL. +func DiscoverAPI(baseURL string) (*DiscoveredAPI, error) { + res, httpErr := http.Get(baseURL) + if httpErr != nil { + return nil, httpErr + } + if res.Header.Get("Link") != "" { + discoveredURL, linkErr := linkHeader(res) + if linkErr != nil { + return nil, linkErr + } + return &DiscoveredAPI{ + BaseURL: baseURL, + DiscoveredURL: discoveredURL, + ViaHeader: true, + }, nil + } + discoveredURL, linkErr := extractLinkFromHTML(res) + if linkErr != nil { + return nil, linkErr + } + return &DiscoveredAPI{ + BaseURL: baseURL, + DiscoveredURL: discoveredURL, + ViaHTML: true, + }, nil +} + +func linkHeader(resp *http.Response) (string, error) { + for _, link := range linkheader.Parse(resp.Header.Get("Link")) { + if link.Rel == "https://api.w.org/" { + return link.URL, nil + } + } + return "", nil +} + +func extractLinkFromHTML(resp *http.Response) (string, error) { + doc, docErr := goquery.NewDocumentFromResponse(resp) + if docErr != nil { + return "", docErr + } + href, hrefExists := doc.Find(`link[rel="https://api.w.org/"]`).Attr("href") + if hrefExists { + return href, nil + } + return "", nil +} From e8fe6297069edb9087bfc11b6e0a0b151da58a9e Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 25 Dec 2017 15:47:30 -0500 Subject: [PATCH 05/46] Add settings --- client.go | 7 +++++++ settings.go | 34 ++++++++++++++++++++++++++++++++++ settings_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 settings.go create mode 100644 settings_test.go diff --git a/client.go b/client.go index 5abf072..c48f955 100644 --- a/client.go +++ b/client.go @@ -24,6 +24,7 @@ const ( CollectionTerms = "terms" CollectionStatuses = "statuses" CollectionTypes = "types" + CollectionSettings = "settings" ) type GeneralError struct { @@ -128,6 +129,12 @@ func (client *Client) Types() *TypesCollection { url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTypes), } } +func (client *Client) Settings() *SettingsCollection { + return &SettingsCollection{ + client: client, + url: fmt.Sprintf("%v/%v", client.baseURL, CollectionSettings), + } +} func (client *Client) List(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { client.req.TargetType = "json" diff --git a/settings.go b/settings.go new file mode 100644 index 0000000..bf4e91f --- /dev/null +++ b/settings.go @@ -0,0 +1,34 @@ +package wordpress + +import ( + "net/http" +) + +type Settings struct { + Title string `json:"title"` + Description string `json:"description"` + URL string `json:"url"` + Email string `json:"email"` + Timezone string `json:"timezone"` + DateFormat string `json:"date_format"` + TimeFormat string `json:"time_format"` + StartOfWeek int `json:"start_of_week"` + Language string `json:"language"` + UseSmilies bool `json:"use_smilies"` + DefaultCategory int `json:"default_category"` + DefaultPostFormat string `json:"default_post_format"` + PostsPerPage int `json:"posts_per_page"` + DefaultPingStatus string `json:"default_ping_status"` + DefaultCommentStatus string `json:"default_comment_status"` +} + +type SettingsCollection struct { + client *Client + url string +} + +func (col *SettingsCollection) List() (*Settings, *http.Response, []byte, error) { + var settings Settings + resp, body, err := col.client.List(col.url, nil, &settings) + return &settings, resp, body, err +} diff --git a/settings_test.go b/settings_test.go new file mode 100644 index 0000000..c5b7322 --- /dev/null +++ b/settings_test.go @@ -0,0 +1,24 @@ +package wordpress_test + +import ( + "net/http" + "testing" +) + +func TestSettingsList(t *testing.T) { + client := initTestClient() + + settings, resp, body, err := client.Settings().List() + if err != nil { + t.Errorf("Should not return error: %v", err.Error()) + } + if body == nil { + t.Errorf("body should not be nil") + } + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected 200 StatusOK, got %v", resp.Status) + } + if settings == nil { + t.Errorf("Should not return nil settings") + } +} From ffc021e30e7d4dfbddbfdf8b56f3fba18fa1219f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E6=9F=8F=E7=B6=AD?= Date: Wed, 17 Jan 2018 17:10:38 +0800 Subject: [PATCH 06/46] support jwt/ bearer token auth --- client.go | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index e1163ad..ff90fe5 100644 --- a/client.go +++ b/client.go @@ -36,6 +36,8 @@ type Options struct { // Basic Auth Username string Password string + + JWTToken string // TODO: support OAuth authentication } @@ -56,7 +58,14 @@ func newHTTPClient() *gorequest.SuperAgent { } func NewClient(options *Options) *Client { - req := newHTTPClient().SetBasicAuth(options.Username, options.Password) + req := newHTTPClient() + + if options.Username != "" && options.Password != "" { + req = req.SetBasicAuth(options.Username, options.Password) + } else if options.JWTToken != "" { + req = req.AppendHeader("Authorization", options.JWTToken) + } + req = req.RedirectPolicy(func(r gorequest.Request, via []gorequest.Request) error { // perform BasicAuth on each redirect request. // (requests are cookie-less; so we need to keep re-auth-ing again) @@ -129,7 +138,13 @@ func (client *Client) Types() *TypesCollection { func (client *Client) List(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { client.req.TargetType = "json" - resp, body, errSlice := client.req.Get(url).Query(params).EndBytes() + + req := client.req.Get(url) + if client.options.JWTToken != "" { + req.Set("Authorization", client.options.JWTToken) + } + resp, body, errSlice := req.Query(params).EndBytes() + if errSlice != nil && len(errSlice) > 0 { return nil, body, errSlice[len(errSlice)-1] } @@ -137,10 +152,14 @@ func (client *Client) List(url string, params interface{}, result interface{}) ( _resp := http.Response(*resp) return &_resp, body, err } + func (client *Client) Create(url string, content interface{}, result interface{}) (*http.Response, []byte, error) { contentVal := unpackInterfacePointer(content) client.req.TargetType = "json" req := client.req.Post(url).Send(contentVal) + if client.options.JWTToken != "" { + req.Set("Authorization", client.options.JWTToken) + } resp, body, errSlice := req.EndBytes() if errSlice != nil && len(errSlice) > 0 { return nil, body, errSlice[len(errSlice)-1] @@ -150,13 +169,21 @@ func (client *Client) Create(url string, content interface{}, result interface{} return &_resp, body, err } func (client *Client) Get(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { + client.req = client.req.AppendHeader("Authorization", client.options.JWTToken) + client.req.TargetType = "json" - resp, body, errSlice := client.req.Get(url).Query(params).EndBytes() + req := client.req.Get(url) + if client.options.JWTToken != "" { + req.Set("Authorization", client.options.JWTToken) + } + resp, body, errSlice := req.Query(params).EndBytes() + if errSlice != nil && len(errSlice) > 0 { return nil, body, errSlice[len(errSlice)-1] } err := unmarshallResponse(resp, body, result) _resp := http.Response(*resp) + return &_resp, body, err } func (client *Client) Update(url string, content interface{}, result interface{}) (*http.Response, []byte, error) { @@ -166,6 +193,11 @@ func (client *Client) Update(url string, content interface{}, result interface{} client.req.TargetType = "json" req := client.req.Post(url).Send(contentVal) req.Set("HTTP_X_HTTP_METHOD_OVERRIDE", "PUT") + + if client.options.JWTToken != "" { + req.Set("Authorization", client.options.JWTToken) + } + resp, body, errSlice := req.EndBytes() if errSlice != nil && len(errSlice) > 0 { return nil, body, errSlice[len(errSlice)-1] @@ -178,6 +210,11 @@ func (client *Client) Delete(url string, params interface{}, result interface{}) client.req.TargetType = "json" req := client.req.Get(url).Query(params).Query("_method=DELETE") req.Set("HTTP_X_HTTP_METHOD_OVERRIDE", "DELETE") + + if client.options.JWTToken != "" { + req.Set("Authorization", client.options.JWTToken) + } + resp, body, errSlice := req.End() by := []byte(body) if errSlice != nil && len(errSlice) > 0 { @@ -203,6 +240,9 @@ func (client *Client) PostData(url string, content []byte, contentType string, f req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Disposition", fmt.Sprintf("filename=%v", filename)) + if client.options.JWTToken != "" { + req.Header.Set("Authorization", client.options.JWTToken) + } // Add basic auth req.SetBasicAuth(s.BasicAuth.Username, s.BasicAuth.Password) From 7bd6de0cfb26568e7d0fd4a3ac56e1e81378132f Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Fri, 26 Jan 2018 16:31:06 -0800 Subject: [PATCH 07/46] Make registered date a Time field --- users.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users.go b/users.go index dcf8186..8878567 100644 --- a/users.go +++ b/users.go @@ -23,7 +23,7 @@ type User struct { Link string `json:"link,omitempty"` Name string `json:"name,omitempty"` Nickname string `json:"nickname,omitempty"` - RegisteredDate string `json:"registered_date,omitempty"` + RegisteredDate Time `json:"registered_date,omitempty"` Roles []string `json:"roles,omitempty"` Slug string `json:"slug,omitempty"` URL string `json:"url,omitempty"` From 15a7d062c29f5e9797936b7fb9e535d3ca6a9fb7 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Fri, 26 Jan 2018 16:34:46 -0800 Subject: [PATCH 08/46] Add Locale field to User --- users.go | 1 + 1 file changed, 1 insertion(+) diff --git a/users.go b/users.go index 8878567..a2cf151 100644 --- a/users.go +++ b/users.go @@ -29,6 +29,7 @@ type User struct { URL string `json:"url,omitempty"` Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` + Locale string `json:"locale,omitempty"` } type UsersCollection struct { From cea3b975dde600cc62c7a280ae59886ae123c6f1 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Fri, 26 Jan 2018 16:46:13 -0800 Subject: [PATCH 09/46] Support time with zone info for Users.RegisteredDate --- time.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/time.go b/time.go index ff9d158..4ac23c1 100644 --- a/time.go +++ b/time.go @@ -11,12 +11,24 @@ type Time struct { // 2017-12-25T09:54:42 const timeLayout = "2006-01-02T15:04:05" -func (t *Time) UnmarshalJSON(b []byte) (err error) { +// "2017-09-24T13:28:06+00:00" +const timeWithZoneLayout = "2006-01-02T15:04:05-07:00" + +func (t *Time) UnmarshalJSON(b []byte) error { if b[0] == '"' && b[len(b)-1] == '"' { b = b[1 : len(b)-1] } - t.Time, err = time.Parse(timeLayout, string(b)) - return + tTime, err := time.Parse(timeLayout, string(b)) + if err != nil { + altTime, altErr := time.Parse(timeWithZoneLayout, string(b)) + if altErr != nil { + return err + } else { + t.Time = altTime + } + } + t.Time = tTime + return nil } func (t *Time) MarshalJSON() ([]byte, error) { From 39b92b7bb812e96b2d57ea34a97f40ef3674ccc3 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 27 Jan 2018 22:43:20 -0800 Subject: [PATCH 10/46] Expose time formats --- time.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/time.go b/time.go index 4ac23c1..e7a3c63 100644 --- a/time.go +++ b/time.go @@ -9,18 +9,18 @@ type Time struct { } // 2017-12-25T09:54:42 -const timeLayout = "2006-01-02T15:04:05" +const TimeLayout = "2006-01-02T15:04:05" // "2017-09-24T13:28:06+00:00" -const timeWithZoneLayout = "2006-01-02T15:04:05-07:00" +const TimeWithZoneLayout = "2006-01-02T15:04:05-07:00" func (t *Time) UnmarshalJSON(b []byte) error { if b[0] == '"' && b[len(b)-1] == '"' { b = b[1 : len(b)-1] } - tTime, err := time.Parse(timeLayout, string(b)) + tTime, err := time.Parse(TimeLayout, string(b)) if err != nil { - altTime, altErr := time.Parse(timeWithZoneLayout, string(b)) + altTime, altErr := time.Parse(TimeWithZoneLayout, string(b)) if altErr != nil { return err } else { @@ -32,5 +32,5 @@ func (t *Time) UnmarshalJSON(b []byte) error { } func (t *Time) MarshalJSON() ([]byte, error) { - return []byte(t.Time.Format(timeLayout)), nil + return []byte(t.Time.Format(TimeLayout)), nil } From 6a80e88c922e7253c5ac7c2afbc454a265615594 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 27 Jan 2018 23:04:23 -0800 Subject: [PATCH 11/46] Fill in post struct more --- posts.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/posts.go b/posts.go index 01727e9..4d42d45 100644 --- a/posts.go +++ b/posts.go @@ -63,29 +63,33 @@ type Caption struct { } type Post struct { - collection *PostsCollection `json:"-,omitempty"` + collection *PostsCollection - ID int `json:"id,omitempty"` + Author int `json:"author,omitempty"` + Categories []int `json:"categories,omitempty"` + CommentStatus string `json:"comment_status,omitempty"` + Content Content `json:"content,omitempty"` Date Time `json:"date,omitempty"` DateGMT Time `json:"date_gmt,omitempty"` + Excerpt Excerpt `json:"excerpt,omitempty"` + FeaturedMedia int `json:"featured_media,omitempty"` + Format string `json:"format,omitempty"` GUID GUID `json:"guid,omitempty"` - FeaturedMedia int `json:"featured_media"` - Categories []int `json:"categories"` + ID int `json:"id,omitempty"` Link string `json:"link,omitempty"` Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modified_gmt,omitempty"` ModifiedGMT Time `json:"modifiedGMT,omitempty"` Password string `json:"password,omitempty"` + PingStatus string `json:"ping_status,omitempty"` Slug string `json:"slug,omitempty"` Status string `json:"status,omitempty"` - Type string `json:"type,omitempty"` - Title Title `json:"title,omitempty"` - Content Content `json:"content,omitempty"` - Author int `json:"author,omitempty"` - Excerpt Excerpt `json:"excerpt,omitempty"` - CommentStatus string `json:"comment_status,omitempty"` - PingStatus string `json:"ping_status,omitempty"` - Format string `json:"format,omitempty"` Sticky bool `json:"sticky,omitempty"` + Tags []int `json:"tags,omitempty"` + Template string `json:"template,omitempty"` + Title Title `json:"title,omitempty"` + Type string `json:"type,omitempty"` + WpsSubtitle string `json:"wps_subtitle,omitempty"` } func (entity *Post) setCollection(col *PostsCollection) { From 4f91da2f0369e4c1fef956568b56ace7d8e3d9cf Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 27 Jan 2018 23:12:31 -0800 Subject: [PATCH 12/46] Change package URL --- README.md | 16 ++++++++-------- client_test.go | 3 ++- comments_test.go | 3 ++- media_test.go | 5 +++-- pages_meta_test.go | 3 ++- pages_revisions_test.go | 3 ++- pages_test.go | 3 ++- posts_meta_test.go | 3 ++- posts_revisions_test.go | 3 ++- posts_terms_category_test.go | 3 ++- posts_terms_tag_test.go | 3 ++- posts_test.go | 3 ++- terms_category_test.go | 3 ++- terms_tag_test.go | 3 ++- users_test.go | 3 ++- 15 files changed, 37 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f9478d2..ec9a025 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Golang client library for WP-API (Wordpress REST API) ## Installation ```bash -go get github.com/sogko/go-wordpress +go get github.com/robbiet480/go-wordpress ``` @@ -16,7 +16,7 @@ go get github.com/sogko/go-wordpress package main import ( - "github.com/sogko/go-wordpress" + "github.com/robbiet480/go-wordpress" "net/http" ) @@ -28,21 +28,21 @@ func main() { Username: USER, Password: PASSWORD, }) - + // for eg, to get current user (GET /users/me) currentUser, resp, body, _ := client.Users().Me() if resp.StatusCode != http.StatusOK { // handle error } - + // `body` will contain raw JSON body in []bytes - + // Or you can use your own structs (for custom endpoints, for example) // Below is the equivalent of `client.Posts().Get(100, nil)` var obj MyCustomPostStruct resp, body, err := client.Get("/posts/100", nil, &obj) // ... - + log.Println("Current user", currentUser) } @@ -80,12 +80,12 @@ export WP_API_URL=http://192.168.99.100:32777/wp-json/wp/v2 export WP_USER= export WP_PASSWD= -cd /github.com/sogko/go-wordpress +cd /github.com/robbiet480/go-wordpress go test ``` ## TODO -- [ ] `godoc` documentation, so its easier for library users to map the REST APIs to library calls +- [ ] `godoc` documentation, so its easier for library users to map the REST APIs to library calls - [ ] Test `comments` API endpoint. (Currently, already implemented but not tested due to WP-API issues with creating comments reliably) - [ ] Support OAuth authentication diff --git a/client_test.go b/client_test.go index 86af5ae..dd22e24 100644 --- a/client_test.go +++ b/client_test.go @@ -1,9 +1,10 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "os" "testing" + + "github.com/robbiet480/go-wordpress" ) var USER string = os.Getenv("WP_USER") diff --git a/comments_test.go b/comments_test.go index 828e7e8..65da208 100644 --- a/comments_test.go +++ b/comments_test.go @@ -1,10 +1,11 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "log" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func factoryComment(postID int) wordpress.Comment { diff --git a/media_test.go b/media_test.go index 8d1da34..0a98a87 100644 --- a/media_test.go +++ b/media_test.go @@ -1,16 +1,17 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "io/ioutil" "net/http" "os" "testing" + + "github.com/robbiet480/go-wordpress" ) func factoryMediaFileUpload(t *testing.T) *wordpress.MediaUploadOptions { - // assuming current-working directory `{GO_WORKSPACE_PATH}/src/github.com/sogko/go-wordpress` + // assuming current-working directory `{GO_WORKSPACE_PATH}/src/github.com/robbiet480/go-wordpress` path := "./test-data/test-media.jpg" // prepare file to upload diff --git a/pages_meta_test.go b/pages_meta_test.go index 4d1043d..c860ef7 100644 --- a/pages_meta_test.go +++ b/pages_meta_test.go @@ -1,9 +1,10 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func cleanUpPageMeta(t *testing.T, page *wordpress.Page, metaId int) { diff --git a/pages_revisions_test.go b/pages_revisions_test.go index 99bc09e..0c5b81a 100644 --- a/pages_revisions_test.go +++ b/pages_revisions_test.go @@ -2,10 +2,11 @@ package wordpress_test import ( "fmt" - "github.com/sogko/go-wordpress" "net/http" "testing" "time" + + "github.com/robbiet480/go-wordpress" ) func getLatestRevisionForPage(t *testing.T, page *wordpress.Page) *wordpress.Revision { diff --git a/pages_test.go b/pages_test.go index a0e91f0..23d7aad 100644 --- a/pages_test.go +++ b/pages_test.go @@ -2,10 +2,11 @@ package wordpress_test import ( "fmt" - "github.com/sogko/go-wordpress" "log" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func factoryPage() wordpress.Page { diff --git a/posts_meta_test.go b/posts_meta_test.go index 91e98e9..fd0b35a 100644 --- a/posts_meta_test.go +++ b/posts_meta_test.go @@ -1,9 +1,10 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func cleanUpPostMeta(t *testing.T, post *wordpress.Post, metaId int) { diff --git a/posts_revisions_test.go b/posts_revisions_test.go index fdeb1c2..bc9bc6a 100644 --- a/posts_revisions_test.go +++ b/posts_revisions_test.go @@ -2,10 +2,11 @@ package wordpress_test import ( "fmt" - "github.com/sogko/go-wordpress" "net/http" "testing" "time" + + "github.com/robbiet480/go-wordpress" ) func getLatestRevisionForPost(t *testing.T, post *wordpress.Post) *wordpress.Revision { diff --git a/posts_terms_category_test.go b/posts_terms_category_test.go index c84b7bf..72b6ad1 100644 --- a/posts_terms_category_test.go +++ b/posts_terms_category_test.go @@ -1,9 +1,10 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func cleanUpPostsTermsCategory(t *testing.T, postID int, id int) { diff --git a/posts_terms_tag_test.go b/posts_terms_tag_test.go index 4ded790..5158e7a 100644 --- a/posts_terms_tag_test.go +++ b/posts_terms_tag_test.go @@ -1,9 +1,10 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func cleanUpPostsTermsTag(t *testing.T, postID int, id int) { diff --git a/posts_test.go b/posts_test.go index a3c412e..4dc52d0 100644 --- a/posts_test.go +++ b/posts_test.go @@ -2,10 +2,11 @@ package wordpress_test import ( "fmt" - "github.com/sogko/go-wordpress" "log" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func factoryPost() wordpress.Post { diff --git a/terms_category_test.go b/terms_category_test.go index 4fa66eb..77ef6b6 100644 --- a/terms_category_test.go +++ b/terms_category_test.go @@ -1,10 +1,11 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "log" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func factoryTermsCategory() *wordpress.Term { diff --git a/terms_tag_test.go b/terms_tag_test.go index 239cae8..b33e249 100644 --- a/terms_tag_test.go +++ b/terms_tag_test.go @@ -1,9 +1,10 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func factoryTermsTag() *wordpress.Term { diff --git a/users_test.go b/users_test.go index 48b37d6..88a9b22 100644 --- a/users_test.go +++ b/users_test.go @@ -1,9 +1,10 @@ package wordpress_test import ( - "github.com/sogko/go-wordpress" "net/http" "testing" + + "github.com/robbiet480/go-wordpress" ) func factoryUser() *wordpress.User { From 2f93c3ccb03f352b5ba1d3322956592e5e7a565a Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 27 Jan 2018 23:15:42 -0800 Subject: [PATCH 13/46] go vet cleanup --- pages.go | 2 +- pages_meta_test.go | 2 +- pages_revisions_test.go | 2 +- posts_meta_test.go | 2 +- posts_revisions_test.go | 2 +- posts_terms_category_test.go | 2 +- posts_terms_tag_test.go | 2 +- terms_category_test.go | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pages.go b/pages.go index a844654..28e8396 100644 --- a/pages.go +++ b/pages.go @@ -6,7 +6,7 @@ import ( ) type Page struct { - collection *PagesCollection `json:"-"` + collection *PagesCollection ID int `json:"id,omitempty"` Date Time `json:"date,omitempty"` diff --git a/pages_meta_test.go b/pages_meta_test.go index c860ef7..9111a73 100644 --- a/pages_meta_test.go +++ b/pages_meta_test.go @@ -33,7 +33,7 @@ func TestPagesMeta_InvalidCall(t *testing.T) { invalidPage := wordpress.Page{} invalidMeta := invalidPage.Meta() if invalidMeta != nil { - t.Error("Expected meta to be nil, %v", invalidMeta) + t.Errorf("Expected meta to be nil, %v", invalidMeta) } } diff --git a/pages_revisions_test.go b/pages_revisions_test.go index 0c5b81a..cdb183f 100644 --- a/pages_revisions_test.go +++ b/pages_revisions_test.go @@ -35,7 +35,7 @@ func TestPagesRevisions_InvalidCall(t *testing.T) { invalidPage := wordpress.Page{} invalidRevisions := invalidPage.Revisions() if invalidRevisions != nil { - t.Error("Expected revisions to be nil, %v", invalidRevisions) + t.Errorf("Expected revisions to be nil, %v", invalidRevisions) } } diff --git a/posts_meta_test.go b/posts_meta_test.go index fd0b35a..cd05ecd 100644 --- a/posts_meta_test.go +++ b/posts_meta_test.go @@ -33,7 +33,7 @@ func TestPostsMeta_InvalidCall(t *testing.T) { invalidPost := wordpress.Post{} invalidMeta := invalidPost.Meta() if invalidMeta != nil { - t.Error("Expected meta to be nil, %v", invalidMeta) + t.Errorf("Expected meta to be nil, %v", invalidMeta) } } diff --git a/posts_revisions_test.go b/posts_revisions_test.go index bc9bc6a..67d5b2c 100644 --- a/posts_revisions_test.go +++ b/posts_revisions_test.go @@ -35,7 +35,7 @@ func TestPostsRevisions_InvalidCall(t *testing.T) { invalidPost := wordpress.Post{} invalidRevisions := invalidPost.Revisions() if invalidRevisions != nil { - t.Error("Expected revisions to be nil, %v", invalidRevisions) + t.Errorf("Expected revisions to be nil, %v", invalidRevisions) } } diff --git a/posts_terms_category_test.go b/posts_terms_category_test.go index 72b6ad1..a49d66b 100644 --- a/posts_terms_category_test.go +++ b/posts_terms_category_test.go @@ -53,7 +53,7 @@ func TestPostsTermsCategory_InvalidCall(t *testing.T) { invalidPost := wordpress.Post{} invalidTerms := invalidPost.Terms() if invalidTerms != nil { - t.Error("Expected meta to be nil, %v", invalidTerms) + t.Errorf("Expected meta to be nil, %v", invalidTerms) } } diff --git a/posts_terms_tag_test.go b/posts_terms_tag_test.go index 5158e7a..ce8ebc8 100644 --- a/posts_terms_tag_test.go +++ b/posts_terms_tag_test.go @@ -54,7 +54,7 @@ func TestPostsTermsTag_InvalidCall(t *testing.T) { invalidPost := wordpress.Post{} invalidTerms := invalidPost.Terms() if invalidTerms != nil { - t.Error("Expected meta to be nil, %v", invalidTerms) + t.Errorf("Expected meta to be nil, %v", invalidTerms) } } diff --git a/terms_category_test.go b/terms_category_test.go index 77ef6b6..15ed5ad 100644 --- a/terms_category_test.go +++ b/terms_category_test.go @@ -165,7 +165,7 @@ func TestTermsCategoryCreate_Existing(t *testing.T) { t.Fatalf("Unexpected error response from server, unable to unmarshall message %v", err.Error()) } if len(serverErrors) != 1 { - t.Errorf("Expected one error", len(serverErrors)) + t.Error("Expected one error", len(serverErrors)) } if serverErrors[0].Code != "term_exists" { t.Errorf("Unexpected err.code, %v != term_exists", serverErrors[0].Code) From 5b3b89d6d10e3707cd5f7ff2a7f1ab9d34bcabd6 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 27 Jan 2018 23:16:05 -0800 Subject: [PATCH 14/46] Remove duplicate ModifiedGMT field --- posts.go | 1 - 1 file changed, 1 deletion(-) diff --git a/posts.go b/posts.go index 4d42d45..dcd51e0 100644 --- a/posts.go +++ b/posts.go @@ -79,7 +79,6 @@ type Post struct { Link string `json:"link,omitempty"` Modified Time `json:"modified,omitempty"` ModifiedGMT Time `json:"modified_gmt,omitempty"` - ModifiedGMT Time `json:"modifiedGMT,omitempty"` Password string `json:"password,omitempty"` PingStatus string `json:"ping_status,omitempty"` Slug string `json:"slug,omitempty"` From d178d45d774c5327ee9e3ef3148184ba8182952d Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 27 Jan 2018 23:21:34 -0800 Subject: [PATCH 15/46] Add categories and tags support --- categories.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ client.go | 14 ++++++++++++++ tags.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 categories.go create mode 100644 tags.go diff --git a/categories.go b/categories.go new file mode 100644 index 0000000..8de443a --- /dev/null +++ b/categories.go @@ -0,0 +1,51 @@ +package wordpress + +import ( + "fmt" + "net/http" +) + +type Category struct { + ID int `json:"id"` + Count int `json:"count"` + Description string `json:"description"` + Link string `json:"link"` + Name string `json:"name"` + Slug string `json:"slug"` + Taxonomy string `json:"taxonomy"` + Parent int `json:"parent"` +} + +type CategoriesCollection struct { + client *Client + url string +} + +func (col *CategoriesCollection) List(params interface{}) ([]Category, *http.Response, []byte, error) { + var categories []Category + resp, body, err := col.client.List(col.url, params, &categories) + return categories, resp, body, err +} +func (col *CategoriesCollection) Create(new *Category) (*Category, *http.Response, []byte, error) { + var created Category + resp, body, err := col.client.Create(col.url, new, &created) + return &created, resp, body, err +} +func (col *CategoriesCollection) Get(id int, params interface{}) (*Category, *http.Response, []byte, error) { + var entity Category + entityURL := fmt.Sprintf("%v/%v", col.url, id) + resp, body, err := col.client.Get(entityURL, params, &entity) + return &entity, resp, body, err +} +func (col *CategoriesCollection) Update(id int, post *Category) (*Category, *http.Response, []byte, error) { + var updated Category + entityURL := fmt.Sprintf("%v/%v", col.url, id) + resp, body, err := col.client.Update(entityURL, post, &updated) + return &updated, resp, body, err +} +func (col *CategoriesCollection) Delete(id int, params interface{}) (*Category, *http.Response, []byte, error) { + var deleted Category + entityURL := fmt.Sprintf("%v/%v", col.url, id) + resp, body, err := col.client.Delete(entityURL, params, &deleted) + return &deleted, resp, body, err +} diff --git a/client.go b/client.go index 9b62d5e..025dd54 100644 --- a/client.go +++ b/client.go @@ -25,6 +25,8 @@ const ( CollectionStatuses = "statuses" CollectionTypes = "types" CollectionSettings = "settings" + CollectionCategories = "categories" + CollectionTags = "tags" ) type GeneralError struct { @@ -144,6 +146,18 @@ func (client *Client) Settings() *SettingsCollection { url: fmt.Sprintf("%v/%v", client.baseURL, CollectionSettings), } } +func (client *Client) Categories() *CategoriesCollection { + return &CategoriesCollection{ + client: client, + url: fmt.Sprintf("%v/%v", client.baseURL, CollectionCategories), + } +} +func (client *Client) Tags() *TagsCollection { + return &TagsCollection{ + client: client, + url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTags), + } +} func (client *Client) List(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { client.req.TargetType = "json" diff --git a/tags.go b/tags.go new file mode 100644 index 0000000..a4dd29c --- /dev/null +++ b/tags.go @@ -0,0 +1,50 @@ +package wordpress + +import ( + "fmt" + "net/http" +) + +type Tag struct { + ID int `json:"id,omitempty"` + Count int `json:"count,omitempty"` + Description string `json:"description,omitempty"` + Link string `json:"link,omitempty"` + Name string `json:"name,omitempty"` + Slug string `json:"slug,omitempty"` + Taxonomy string `json:"taxonomy,omitempty"` +} + +type TagsCollection struct { + client *Client + url string +} + +func (col *TagsCollection) List(params interface{}) ([]Tag, *http.Response, []byte, error) { + var tags []Tag + resp, body, err := col.client.List(col.url, params, &tags) + return tags, resp, body, err +} +func (col *TagsCollection) Create(new *Tag) (*Tag, *http.Response, []byte, error) { + var created Tag + resp, body, err := col.client.Create(col.url, new, &created) + return &created, resp, body, err +} +func (col *TagsCollection) Get(id int, params interface{}) (*Tag, *http.Response, []byte, error) { + var entity Tag + entityURL := fmt.Sprintf("%v/%v", col.url, id) + resp, body, err := col.client.Get(entityURL, params, &entity) + return &entity, resp, body, err +} +func (col *TagsCollection) Update(id int, post *Tag) (*Tag, *http.Response, []byte, error) { + var updated Tag + entityURL := fmt.Sprintf("%v/%v", col.url, id) + resp, body, err := col.client.Update(entityURL, post, &updated) + return &updated, resp, body, err +} +func (col *TagsCollection) Delete(id int, params interface{}) (*Tag, *http.Response, []byte, error) { + var deleted Tag + entityURL := fmt.Sprintf("%v/%v", col.url, id) + resp, body, err := col.client.Delete(entityURL, params, &deleted) + return &deleted, resp, body, err +} From f3e28538df477cb598cb8bb2c466199aeb8cfc08 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 3 Feb 2018 14:19:36 -0800 Subject: [PATCH 16/46] Minor styling fixes --- README.md | 2 +- client.go | 27 +++++++++++++++++++++------ terms_category_test.go | 2 +- utils.go | 9 +++++---- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ec9a025..966ddcc 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Before running the tests, ensure that you have set up your test environment - [WP REST API Meta Endpoints plugin](https://github.com/WP-API/wp-api-meta-endpoints) (for Meta endpoints) ### Setting up test environment -- Install prequisits (see above) +- Install prequisites (see above) - Import [./test-data/go-wordpress.wordpress.2015-08-23.xml](./test-data/go-wordpress.wordpress.2015-08-23.xml) to your local test Wordpress installation - Upload at least one media to your Wordpress installation (Admin > Media > Upload) - Edit one (1) most recent Post to create a revision diff --git a/client.go b/client.go index 025dd54..626619e 100644 --- a/client.go +++ b/client.go @@ -92,66 +92,77 @@ func (client *Client) Users() *UsersCollection { url: fmt.Sprintf("%v/%v", client.baseURL, CollectionUsers), } } + func (client *Client) Posts() *PostsCollection { return &PostsCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionPosts), } } + func (client *Client) Pages() *PagesCollection { return &PagesCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionPages), } } + func (client *Client) Media() *MediaCollection { return &MediaCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionMedia), } } + func (client *Client) Comments() *CommentsCollection { return &CommentsCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionComments), } } + func (client *Client) Taxonomies() *TaxonomiesCollection { return &TaxonomiesCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTaxonomies), } } + func (client *Client) Terms() *TermsCollection { return &TermsCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTerms), } } + func (client *Client) Statuses() *StatusesCollection { return &StatusesCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionStatuses), } } + func (client *Client) Types() *TypesCollection { return &TypesCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTypes), } } + func (client *Client) Settings() *SettingsCollection { return &SettingsCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionSettings), } } + func (client *Client) Categories() *CategoriesCollection { return &CategoriesCollection{ client: client, url: fmt.Sprintf("%v/%v", client.baseURL, CollectionCategories), } } + func (client *Client) Tags() *TagsCollection { return &TagsCollection{ client: client, @@ -171,7 +182,7 @@ func (client *Client) List(url string, params interface{}, result interface{}) ( if errSlice != nil && len(errSlice) > 0 { return nil, body, errSlice[len(errSlice)-1] } - err := unmarshallResponse(resp, body, result) + err := unmarshalResponse(resp, body, result) _resp := http.Response(*resp) return &_resp, body, err } @@ -187,10 +198,11 @@ func (client *Client) Create(url string, content interface{}, result interface{} if errSlice != nil && len(errSlice) > 0 { return nil, body, errSlice[len(errSlice)-1] } - err := unmarshallResponse(resp, body, result) + err := unmarshalResponse(resp, body, result) _resp := http.Response(*resp) return &_resp, body, err } + func (client *Client) Get(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { client.req = client.req.AppendHeader("Authorization", client.options.JWTToken) @@ -204,11 +216,12 @@ func (client *Client) Get(url string, params interface{}, result interface{}) (* if errSlice != nil && len(errSlice) > 0 { return nil, body, errSlice[len(errSlice)-1] } - err := unmarshallResponse(resp, body, result) + err := unmarshalResponse(resp, body, result) _resp := http.Response(*resp) return &_resp, body, err } + func (client *Client) Update(url string, content interface{}, result interface{}) (*http.Response, []byte, error) { contentVal := unpackInterfacePointer(content) @@ -225,10 +238,11 @@ func (client *Client) Update(url string, content interface{}, result interface{} if errSlice != nil && len(errSlice) > 0 { return nil, body, errSlice[len(errSlice)-1] } - err := unmarshallResponse(resp, body, result) + err := unmarshalResponse(resp, body, result) _resp := http.Response(*resp) return &_resp, body, err } + func (client *Client) Delete(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { client.req.TargetType = "json" req := client.req.Get(url).Query(params).Query("_method=DELETE") @@ -243,10 +257,11 @@ func (client *Client) Delete(url string, params interface{}, result interface{}) if errSlice != nil && len(errSlice) > 0 { return resp, by, errSlice[len(errSlice)-1] } - err := unmarshallResponse(resp, by, result) + err := unmarshalResponse(resp, by, result) _resp := http.Response(*resp) return &_resp, by, err } + func (client *Client) PostData(url string, content []byte, contentType string, filename string, result interface{}) (*http.Response, []byte, error) { // gorequest does not support POST-ing raw data @@ -288,7 +303,7 @@ func (client *Client) PostData(url string, content []byte, contentType string, f return nil, nil, err } - err = unmarshallResponse(resp, body, result) + err = unmarshalResponse(resp, body, result) _resp := http.Response(*resp) return &_resp, body, err } diff --git a/terms_category_test.go b/terms_category_test.go index 15ed5ad..68f80c1 100644 --- a/terms_category_test.go +++ b/terms_category_test.go @@ -158,7 +158,7 @@ func TestTermsCategoryCreate_Existing(t *testing.T) { // unmarshall error response // We expect server to return "term_exists" error code - serverErrors, err := wordpress.UnmarshallServerError(body) + serverErrors, err := wordpress.UnmarshalServerError(body) if err != nil { cleanUpTermsCategory(t, term.ID) log.Println(string(body)) diff --git a/utils.go b/utils.go index f2a1654..9db7db9 100644 --- a/utils.go +++ b/utils.go @@ -5,15 +5,16 @@ import ( "encoding/json" "errors" "fmt" - "github.com/parnurzeal/gorequest" "log" "net/http" "os" + + "github.com/parnurzeal/gorequest" ) var DEBUG bool = (os.Getenv("DEBUG") == "1") -func unmarshallResponse(resp gorequest.Response, body []byte, result interface{}) error { +func unmarshalResponse(resp gorequest.Response, body []byte, result interface{}) error { var prettyJSON bytes.Buffer err2 := json.Indent(&prettyJSON, body, "", " ") @@ -50,8 +51,8 @@ func _log(v ...interface{}) { log.Println(fmt.Sprintln("[go-wordpress]", v)) } -// UnmarshallServerError A helper function to unmarshall error response from server -func UnmarshallServerError(body []byte) ([]GeneralError, error) { +// UnmarshalServerError A helper function to unmarshal error response from server +func UnmarshalServerError(body []byte) ([]GeneralError, error) { var resp []GeneralError err := json.Unmarshal(body, &resp) if err != nil { From 89c602ed92261bbbf081edfe6219a8eae396ec7e Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 3 Feb 2018 15:14:04 -0800 Subject: [PATCH 17/46] Support getting root API info --- README.md | 4 ++-- client.go | 60 ++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 966ddcc..4aa0e09 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ func main() { // create wp-api client client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json/wp/v2` + BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json` Username: USER, Password: PASSWORD, }) @@ -76,7 +76,7 @@ Before running the tests, ensure that you have set up your test environment ```bash # Set test enviroment -export WP_API_URL=http://192.168.99.100:32777/wp-json/wp/v2 +export WP_API_URL=http://192.168.99.100:32777/wp-json export WP_USER= export WP_PASSWD= diff --git a/client.go b/client.go index 626619e..a048685 100644 --- a/client.go +++ b/client.go @@ -8,6 +8,8 @@ import ( "mime/multipart" "net/http" "reflect" + "strings" + "time" "github.com/parnurzeal/gorequest" ) @@ -79,6 +81,10 @@ func NewClient(options *Options) *Client { log.Println("REDIRECT", r, options.Username, options.Password) return nil }) + if strings.HasSuffix(options.BaseAPIURL, "/wp/v2") { + splitURL := strings.Split(options.BaseAPIURL, "/wp/v2") + options.BaseAPIURL = splitURL[0] + } return &Client{ req: req, options: options, @@ -86,87 +92,117 @@ func NewClient(options *Options) *Client { } } +type RootInfo struct { + Authentication map[string]interface{} `json:"authentication"` + Description string `json:"description"` + GMTOffset int `json:"gmt_offset"` + HomeURL string `json:"home"` + Name string `json:"name"` + Namespaces []string `json:"namespaces"` + PermalinkStructure string `json:"permalink_structure"` + TimezoneString string `json:"timezone_string"` + URL string `json:"url"` + + Location *time.Location `json:"-"` +} + +func (client *Client) BasicInfo() (*RootInfo, *http.Response, []byte, error) { + var entity RootInfo + resp, body, err := client.Get(client.baseURL, nil, &entity) + if err != nil { + return &entity, resp, body, err + } + + location, locationErr := time.LoadLocation(entity.TimezoneString) + if locationErr != nil { + return &entity, resp, body, locationErr + } + entity.Location = location + + return &entity, resp, body, err +} + func (client *Client) Users() *UsersCollection { return &UsersCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionUsers), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionUsers), } } func (client *Client) Posts() *PostsCollection { return &PostsCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionPosts), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionPosts), } } func (client *Client) Pages() *PagesCollection { return &PagesCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionPages), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionPages), } } func (client *Client) Media() *MediaCollection { return &MediaCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionMedia), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionMedia), } } func (client *Client) Comments() *CommentsCollection { return &CommentsCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionComments), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionComments), } } func (client *Client) Taxonomies() *TaxonomiesCollection { return &TaxonomiesCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTaxonomies), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionTaxonomies), } } func (client *Client) Terms() *TermsCollection { return &TermsCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTerms), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionTerms), } } func (client *Client) Statuses() *StatusesCollection { return &StatusesCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionStatuses), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionStatuses), } } func (client *Client) Types() *TypesCollection { return &TypesCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTypes), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionTypes), } } func (client *Client) Settings() *SettingsCollection { return &SettingsCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionSettings), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionSettings), } } func (client *Client) Categories() *CategoriesCollection { return &CategoriesCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionCategories), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionCategories), } } func (client *Client) Tags() *TagsCollection { return &TagsCollection{ client: client, - url: fmt.Sprintf("%v/%v", client.baseURL, CollectionTags), + url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionTags), } } From 6a4824cb66c138cf18783f2ca9b7d1ae346d29e6 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 3 Feb 2018 16:40:09 -0800 Subject: [PATCH 18/46] Support decoding timestamps to timezone of site --- client.go | 6 ++++++ time.go | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/client.go b/client.go index a048685..da72775 100644 --- a/client.go +++ b/client.go @@ -39,6 +39,7 @@ type GeneralError struct { type Options struct { BaseAPIURL string + Location *time.Location // Basic Auth Username string @@ -85,6 +86,11 @@ func NewClient(options *Options) *Client { splitURL := strings.Split(options.BaseAPIURL, "/wp/v2") options.BaseAPIURL = splitURL[0] } + + if options.Location != nil { + Location = options.Location + } + return &Client{ req: req, options: options, diff --git a/time.go b/time.go index e7a3c63..f60eb3e 100644 --- a/time.go +++ b/time.go @@ -4,6 +4,8 @@ import ( "time" ) +var Location = time.UTC + type Time struct { time.Time } @@ -18,16 +20,16 @@ func (t *Time) UnmarshalJSON(b []byte) error { if b[0] == '"' && b[len(b)-1] == '"' { b = b[1 : len(b)-1] } - tTime, err := time.Parse(TimeLayout, string(b)) + zoneTime, err := time.Parse(TimeWithZoneLayout, string(b)) if err != nil { - altTime, altErr := time.Parse(TimeWithZoneLayout, string(b)) + noZoneTime, altErr := time.ParseInLocation(TimeLayout, string(b), Location) if altErr != nil { return err } else { - t.Time = altTime + zoneTime = noZoneTime } } - t.Time = tTime + t.Time = zoneTime return nil } From 4bf43eaed1d079719c31ae7b7c64bc6840285539 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 3 Feb 2018 16:50:57 -0800 Subject: [PATCH 19/46] Add basic info support to DiscoverAPI function --- discovery.go | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/discovery.go b/discovery.go index 310553c..0bcf929 100644 --- a/discovery.go +++ b/discovery.go @@ -12,10 +12,15 @@ type DiscoveredAPI struct { DiscoveredURL string ViaHeader bool ViaHTML bool + Client *Client + BasicInfo *RootInfo } // DiscoverAPI will discover the API root URL for the given base URL. -func DiscoverAPI(baseURL string) (*DiscoveredAPI, error) { +func DiscoverAPI(baseURL string, getRootInfo bool) (*DiscoveredAPI, error) { + discovered := &DiscoveredAPI{ + BaseURL: baseURL, + } res, httpErr := http.Get(baseURL) if httpErr != nil { return nil, httpErr @@ -25,21 +30,30 @@ func DiscoverAPI(baseURL string) (*DiscoveredAPI, error) { if linkErr != nil { return nil, linkErr } - return &DiscoveredAPI{ - BaseURL: baseURL, - DiscoveredURL: discoveredURL, - ViaHeader: true, - }, nil + discovered.DiscoveredURL = discoveredURL + discovered.ViaHeader = true + } else { + discoveredURL, linkErr := extractLinkFromHTML(res) + if linkErr != nil { + return nil, linkErr + } + discovered.DiscoveredURL = discoveredURL + discovered.ViaHTML = true + } + clientOpts := &Options{ + BaseAPIURL: discovered.DiscoveredURL, } - discoveredURL, linkErr := extractLinkFromHTML(res) - if linkErr != nil { - return nil, linkErr + if getRootInfo { + client := NewClient(clientOpts) + info, _, _, basicInfoErr := client.BasicInfo() + if basicInfoErr != nil { + return nil, basicInfoErr + } + clientOpts.Location = info.Location + discovered.BasicInfo = info } - return &DiscoveredAPI{ - BaseURL: baseURL, - DiscoveredURL: discoveredURL, - ViaHTML: true, - }, nil + discovered.Client = NewClient(clientOpts) + return discovered, nil } func linkHeader(resp *http.Response) (string, error) { From e179b32ceef768c8e5e4ebd5fa102ccdc3ca826f Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 3 Feb 2018 18:23:28 -0800 Subject: [PATCH 20/46] Wrap responses to get pagination data --- categories.go | 21 ++++++++++----------- client.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- comments.go | 21 ++++++++++----------- media.go | 17 ++++++++--------- meta.go | 21 ++++++++++----------- pages.go | 23 +++++++++++------------ posts.go | 23 +++++++++++------------ posts_terms.go | 21 ++++++++++----------- revisions.go | 13 ++++++------- settings.go | 8 ++------ statuses.go | 9 ++++----- tags.go | 21 ++++++++++----------- taxonomies.go | 9 ++++----- terms.go | 25 ++++++++++++------------- types.go | 9 ++++----- users.go | 25 ++++++++++++------------- 16 files changed, 170 insertions(+), 146 deletions(-) diff --git a/categories.go b/categories.go index 8de443a..2aeab17 100644 --- a/categories.go +++ b/categories.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type Category struct { @@ -21,31 +20,31 @@ type CategoriesCollection struct { url string } -func (col *CategoriesCollection) List(params interface{}) ([]Category, *http.Response, []byte, error) { +func (col *CategoriesCollection) List(params interface{}) ([]Category, *Response, []byte, error) { var categories []Category resp, body, err := col.client.List(col.url, params, &categories) - return categories, resp, body, err + return categories, newResponse(resp), body, err } -func (col *CategoriesCollection) Create(new *Category) (*Category, *http.Response, []byte, error) { +func (col *CategoriesCollection) Create(new *Category) (*Category, *Response, []byte, error) { var created Category resp, body, err := col.client.Create(col.url, new, &created) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *CategoriesCollection) Get(id int, params interface{}) (*Category, *http.Response, []byte, error) { +func (col *CategoriesCollection) Get(id int, params interface{}) (*Category, *Response, []byte, error) { var entity Category entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } -func (col *CategoriesCollection) Update(id int, post *Category) (*Category, *http.Response, []byte, error) { +func (col *CategoriesCollection) Update(id int, post *Category) (*Category, *Response, []byte, error) { var updated Category entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, resp, body, err + return &updated, newResponse(resp), body, err } -func (col *CategoriesCollection) Delete(id int, params interface{}) (*Category, *http.Response, []byte, error) { +func (col *CategoriesCollection) Delete(id int, params interface{}) (*Category, *Response, []byte, error) { var deleted Category entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } diff --git a/client.go b/client.go index da72775..815028e 100644 --- a/client.go +++ b/client.go @@ -8,6 +8,7 @@ import ( "mime/multipart" "net/http" "reflect" + "strconv" "strings" "time" @@ -55,6 +56,47 @@ type Client struct { baseURL string } +// Response is a WordPress REST API response. This wraps the standard http.Response +// returned from WordPress and provides convenient access to things like +// pagination data. +type Response struct { + *http.Response + + // These fields provide the page values for paginating through a set of + // results. Any or all of these may be set to the zero value for + // responses that are not part of a paginated set, or for which there + // are no additional pages. + + TotalRecords int + TotalPages int +} + +// newResponse creates a new Response for the provided http.Response. +// r must not be nil. +func newResponse(r *http.Response) *Response { + response := &Response{Response: r} + response.populatePageValues() + return response +} + +// populatePageValues parses the HTTP Link response headers and populates the +// various pagination link values in the Response. +func (r *Response) populatePageValues() { + totalRecords, err := strconv.Atoi(r.Header.Get("X-WP-Total")) + if err != nil { + return + } + + r.TotalRecords = totalRecords + + totalPages, err := strconv.Atoi(r.Header.Get("X-WP-TotalPages")) + if err != nil { + return + } + + r.TotalPages = totalPages +} + // Used to create a new SuperAgent object. func newHTTPClient() *gorequest.SuperAgent { client := gorequest.New() @@ -112,20 +154,20 @@ type RootInfo struct { Location *time.Location `json:"-"` } -func (client *Client) BasicInfo() (*RootInfo, *http.Response, []byte, error) { +func (client *Client) BasicInfo() (*RootInfo, *Response, []byte, error) { var entity RootInfo resp, body, err := client.Get(client.baseURL, nil, &entity) if err != nil { - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } location, locationErr := time.LoadLocation(entity.TimezoneString) if locationErr != nil { - return &entity, resp, body, locationErr + return &entity, newResponse(resp), body, locationErr } entity.Location = location - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } func (client *Client) Users() *UsersCollection { diff --git a/comments.go b/comments.go index c3b0d32..5dbf40e 100644 --- a/comments.go +++ b/comments.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type Comment struct { @@ -31,31 +30,31 @@ type CommentsCollection struct { url string } -func (col *CommentsCollection) List(params interface{}) ([]Comment, *http.Response, []byte, error) { +func (col *CommentsCollection) List(params interface{}) ([]Comment, *Response, []byte, error) { var comments []Comment resp, body, err := col.client.List(col.url, params, &comments) - return comments, resp, body, err + return comments, newResponse(resp), body, err } -func (col *CommentsCollection) Create(new *Comment) (*Comment, *http.Response, []byte, error) { +func (col *CommentsCollection) Create(new *Comment) (*Comment, *Response, []byte, error) { var created Comment resp, body, err := col.client.Create(col.url, new, &created) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *CommentsCollection) Get(id int, params interface{}) (*Comment, *http.Response, []byte, error) { +func (col *CommentsCollection) Get(id int, params interface{}) (*Comment, *Response, []byte, error) { var entity Comment entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } -func (col *CommentsCollection) Update(id int, post *Comment) (*Comment, *http.Response, []byte, error) { +func (col *CommentsCollection) Update(id int, post *Comment) (*Comment, *Response, []byte, error) { var updated Comment entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, resp, body, err + return &updated, newResponse(resp), body, err } -func (col *CommentsCollection) Delete(id int, params interface{}) (*Comment, *http.Response, []byte, error) { +func (col *CommentsCollection) Delete(id int, params interface{}) (*Comment, *Response, []byte, error) { var deleted Comment entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } diff --git a/media.go b/media.go index 4ca09fb..4adffb0 100644 --- a/media.go +++ b/media.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type MediaDetailsSizesItem struct { @@ -62,25 +61,25 @@ type MediaCollection struct { url string } -func (col *MediaCollection) List(params interface{}) ([]Media, *http.Response, []byte, error) { +func (col *MediaCollection) List(params interface{}) ([]Media, *Response, []byte, error) { var media []Media resp, body, err := col.client.List(col.url, params, &media) - return media, resp, body, err + return media, newResponse(resp), body, err } -func (col *MediaCollection) Create(options *MediaUploadOptions) (*Media, *http.Response, []byte, error) { +func (col *MediaCollection) Create(options *MediaUploadOptions) (*Media, *Response, []byte, error) { var created Media resp, body, err := col.client.PostData(col.url, options.Data, options.ContentType, options.Filename, &created) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *MediaCollection) Get(id int, params interface{}) (*Media, *http.Response, []byte, error) { +func (col *MediaCollection) Get(id int, params interface{}) (*Media, *Response, []byte, error) { var entity Media entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } -func (col *MediaCollection) Delete(id int, params interface{}) (*Media, *http.Response, []byte, error) { +func (col *MediaCollection) Delete(id int, params interface{}) (*Media, *Response, []byte, error) { var deleted Media entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } diff --git a/meta.go b/meta.go index d2700f3..5330906 100644 --- a/meta.go +++ b/meta.go @@ -3,7 +3,6 @@ package wordpress import ( "fmt" "log" - "net/http" ) type Meta struct { @@ -23,32 +22,32 @@ type MetaCollection struct { parentType string } -func (col *MetaCollection) List(params interface{}) ([]Meta, *http.Response, []byte, error) { +func (col *MetaCollection) List(params interface{}) ([]Meta, *Response, []byte, error) { var meta []Meta resp, body, err := col.client.List(col.url, params, &meta) - return meta, resp, body, err + return meta, newResponse(resp), body, err } -func (col *MetaCollection) Create(new *Meta) (*Meta, *http.Response, []byte, error) { +func (col *MetaCollection) Create(new *Meta) (*Meta, *Response, []byte, error) { var created Meta resp, body, err := col.client.Create(col.url, new, &created) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *MetaCollection) Get(id int, params interface{}) (*Meta, *http.Response, []byte, error) { +func (col *MetaCollection) Get(id int, params interface{}) (*Meta, *Response, []byte, error) { var meta Meta entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &meta) - return &meta, resp, body, err + return &meta, newResponse(resp), body, err } -func (col *MetaCollection) Update(id int, meta *Meta) (*Meta, *http.Response, []byte, error) { +func (col *MetaCollection) Update(id int, meta *Meta) (*Meta, *Response, []byte, error) { var updated Meta entityURL := fmt.Sprintf("%v/%v", col.url, id) log.Println("URL", entityURL) resp, body, err := col.client.Update(entityURL, meta, &updated) - return &updated, resp, body, err + return &updated, newResponse(resp), body, err } -func (col *MetaCollection) Delete(id int, params interface{}) (*MetaDeletedResponse, *http.Response, []byte, error) { +func (col *MetaCollection) Delete(id int, params interface{}) (*MetaDeletedResponse, *Response, []byte, error) { var response MetaDeletedResponse entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, params, &response) - return &response, resp, body, err + return &response, newResponse(resp), body, err } diff --git a/pages.go b/pages.go index 28e8396..513b405 100644 --- a/pages.go +++ b/pages.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type Page struct { @@ -61,7 +60,7 @@ func (entity *Page) Revisions() *RevisionsCollection { } } -func (entity *Page) Populate(params interface{}) (*Page, *http.Response, []byte, error) { +func (entity *Page) Populate(params interface{}) (*Page, *Response, []byte, error) { return entity.collection.Get(entity.ID, params) } @@ -71,7 +70,7 @@ type PagesCollection struct { entityURL string } -func (col *PagesCollection) List(params interface{}) ([]Page, *http.Response, []byte, error) { +func (col *PagesCollection) List(params interface{}) ([]Page, *Response, []byte, error) { var pages []Page resp, body, err := col.client.List(col.url, params, &pages) @@ -80,17 +79,17 @@ func (col *PagesCollection) List(params interface{}) ([]Page, *http.Response, [] p.setCollection(col) } - return pages, resp, body, err + return pages, newResponse(resp), body, err } -func (col *PagesCollection) Create(new *Page) (*Page, *http.Response, []byte, error) { +func (col *PagesCollection) Create(new *Page) (*Page, *Response, []byte, error) { var created Page resp, body, err := col.client.Create(col.url, new, &created) created.setCollection(col) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *PagesCollection) Get(id int, params interface{}) (*Page, *http.Response, []byte, error) { +func (col *PagesCollection) Get(id int, params interface{}) (*Page, *Response, []byte, error) { var entity Page entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) @@ -98,7 +97,7 @@ func (col *PagesCollection) Get(id int, params interface{}) (*Page, *http.Respon // set collection object for each entity which has sub-collection entity.setCollection(col) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } func (col *PagesCollection) Entity(id int) *Page { entity := Page{ @@ -108,7 +107,7 @@ func (col *PagesCollection) Entity(id int) *Page { return &entity } -func (col *PagesCollection) Update(id int, page *Page) (*Page, *http.Response, []byte, error) { +func (col *PagesCollection) Update(id int, page *Page) (*Page, *Response, []byte, error) { var updated Page entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Update(entityURL, page, &updated) @@ -116,9 +115,9 @@ func (col *PagesCollection) Update(id int, page *Page) (*Page, *http.Response, [ // set collection object for each entity which has sub-collection updated.setCollection(col) - return &updated, resp, body, err + return &updated, newResponse(resp), body, err } -func (col *PagesCollection) Delete(id int, params interface{}) (*Page, *http.Response, []byte, error) { +func (col *PagesCollection) Delete(id int, params interface{}) (*Page, *Response, []byte, error) { var deleted Page entityURL := fmt.Sprintf("%v/%v", col.url, id) @@ -127,5 +126,5 @@ func (col *PagesCollection) Delete(id int, params interface{}) (*Page, *http.Res // set collection object for each entity which has sub-collection deleted.setCollection(col) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } diff --git a/posts.go b/posts.go index dcd51e0..5659cd0 100644 --- a/posts.go +++ b/posts.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) const ( @@ -133,7 +132,7 @@ func (entity *Post) Terms() *PostsTermsCollection { url: fmt.Sprintf("%v/%v/%v", entity.collection.url, entity.ID, CollectionTerms), } } -func (entity *Post) Populate(params interface{}) (*Post, *http.Response, []byte, error) { +func (entity *Post) Populate(params interface{}) (*Post, *Response, []byte, error) { return entity.collection.Get(entity.ID, params) } @@ -143,7 +142,7 @@ type PostsCollection struct { entityURL string } -func (col *PostsCollection) List(params interface{}) ([]Post, *http.Response, []byte, error) { +func (col *PostsCollection) List(params interface{}) ([]Post, *Response, []byte, error) { var posts []Post resp, body, err := col.client.List(col.url, params, &posts) @@ -152,17 +151,17 @@ func (col *PostsCollection) List(params interface{}) ([]Post, *http.Response, [] p.setCollection(col) } - return posts, resp, body, err + return posts, newResponse(resp), body, err } -func (col *PostsCollection) Create(new *Post) (*Post, *http.Response, []byte, error) { +func (col *PostsCollection) Create(new *Post) (*Post, *Response, []byte, error) { var created Post resp, body, err := col.client.Create(col.url, new, &created) created.setCollection(col) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *PostsCollection) Get(id int, params interface{}) (*Post, *http.Response, []byte, error) { +func (col *PostsCollection) Get(id int, params interface{}) (*Post, *Response, []byte, error) { var entity Post entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) @@ -170,7 +169,7 @@ func (col *PostsCollection) Get(id int, params interface{}) (*Post, *http.Respon // set collection object for each entity which has sub-collection entity.setCollection(col) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } func (col *PostsCollection) Entity(id int) *Post { entity := Post{ @@ -180,7 +179,7 @@ func (col *PostsCollection) Entity(id int) *Post { return &entity } -func (col *PostsCollection) Update(id int, post *Post) (*Post, *http.Response, []byte, error) { +func (col *PostsCollection) Update(id int, post *Post) (*Post, *Response, []byte, error) { var updated Post entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Update(entityURL, post, &updated) @@ -188,9 +187,9 @@ func (col *PostsCollection) Update(id int, post *Post) (*Post, *http.Response, [ // set collection object for each entity which has sub-collection updated.setCollection(col) - return &updated, resp, body, err + return &updated, newResponse(resp), body, err } -func (col *PostsCollection) Delete(id int, params interface{}) (*Post, *http.Response, []byte, error) { +func (col *PostsCollection) Delete(id int, params interface{}) (*Post, *Response, []byte, error) { var deleted Post entityURL := fmt.Sprintf("%v/%v", col.url, id) @@ -199,5 +198,5 @@ func (col *PostsCollection) Delete(id int, params interface{}) (*Post, *http.Res // set collection object for each entity which has sub-collection deleted.setCollection(col) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } diff --git a/posts_terms.go b/posts_terms.go index e65ebd0..d85a5b0 100644 --- a/posts_terms.go +++ b/posts_terms.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type PostsTerm struct { @@ -23,11 +22,11 @@ type PostsTermsCollection struct { parentType string } -func (col *PostsTermsCollection) List(taxonomy string, params interface{}) ([]PostsTerm, *http.Response, []byte, error) { +func (col *PostsTermsCollection) List(taxonomy string, params interface{}) ([]PostsTerm, *Response, []byte, error) { var terms []PostsTerm url := fmt.Sprintf("%v/%v", col.url, taxonomy) resp, body, err := col.client.List(url, params, &terms) - return terms, resp, body, err + return terms, newResponse(resp), body, err } func (col *PostsTermsCollection) Tag() *PostsTermsTaxonomyCollection { return &PostsTermsTaxonomyCollection{ @@ -50,26 +49,26 @@ type PostsTermsTaxonomyCollection struct { taxonomyBase string } -func (col *PostsTermsTaxonomyCollection) List(params interface{}) ([]PostsTerm, *http.Response, []byte, error) { +func (col *PostsTermsTaxonomyCollection) List(params interface{}) ([]PostsTerm, *Response, []byte, error) { var terms []PostsTerm resp, body, err := col.client.List(col.url, params, &terms) - return terms, resp, body, err + return terms, newResponse(resp), body, err } -func (col *PostsTermsTaxonomyCollection) Create(id int) (*PostsTerm, *http.Response, []byte, error) { +func (col *PostsTermsTaxonomyCollection) Create(id int) (*PostsTerm, *Response, []byte, error) { var created PostsTerm entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Create(entityURL, nil, &created) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *PostsTermsTaxonomyCollection) Get(id int, params interface{}) (*PostsTerm, *http.Response, []byte, error) { +func (col *PostsTermsTaxonomyCollection) Get(id int, params interface{}) (*PostsTerm, *Response, []byte, error) { var entity PostsTerm entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } -func (col *PostsTermsTaxonomyCollection) Delete(id int, params interface{}) (*PostsTerm, *http.Response, []byte, error) { +func (col *PostsTermsTaxonomyCollection) Delete(id int, params interface{}) (*PostsTerm, *Response, []byte, error) { var deleted PostsTerm entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } diff --git a/revisions.go b/revisions.go index f7a121c..6977098 100644 --- a/revisions.go +++ b/revisions.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type Revision struct { @@ -27,23 +26,23 @@ type RevisionsCollection struct { parentType string } -func (col *RevisionsCollection) List(params interface{}) ([]Revision, *http.Response, []byte, error) { +func (col *RevisionsCollection) List(params interface{}) ([]Revision, *Response, []byte, error) { var revisions []Revision resp, body, err := col.client.List(col.url, params, &revisions) - return revisions, resp, body, err + return revisions, newResponse(resp), body, err } -func (col *RevisionsCollection) Get(id int, params interface{}) (*Revision, *http.Response, []byte, error) { +func (col *RevisionsCollection) Get(id int, params interface{}) (*Revision, *Response, []byte, error) { var revision Revision entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &revision) - return &revision, resp, body, err + return &revision, newResponse(resp), body, err } // TODO: file an issue for inconsistent response -func (col *RevisionsCollection) Delete(id int, params interface{}) (bool, *http.Response, []byte, error) { +func (col *RevisionsCollection) Delete(id int, params interface{}) (bool, *Response, []byte, error) { var response bool entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, "force=true", &response) - return response, resp, body, err + return response, newResponse(resp), body, err } diff --git a/settings.go b/settings.go index bf4e91f..a422660 100644 --- a/settings.go +++ b/settings.go @@ -1,9 +1,5 @@ package wordpress -import ( - "net/http" -) - type Settings struct { Title string `json:"title"` Description string `json:"description"` @@ -27,8 +23,8 @@ type SettingsCollection struct { url string } -func (col *SettingsCollection) List() (*Settings, *http.Response, []byte, error) { +func (col *SettingsCollection) List() (*Settings, *Response, []byte, error) { var settings Settings resp, body, err := col.client.List(col.url, nil, &settings) - return &settings, resp, body, err + return &settings, newResponse(resp), body, err } diff --git a/statuses.go b/statuses.go index 59a17e1..14a7133 100644 --- a/statuses.go +++ b/statuses.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type Status struct { @@ -26,15 +25,15 @@ type StatusesCollection struct { url string } -func (col *StatusesCollection) List(params interface{}) (*Statuses, *http.Response, []byte, error) { +func (col *StatusesCollection) List(params interface{}) (*Statuses, *Response, []byte, error) { var statuses Statuses resp, body, err := col.client.List(col.url, params, &statuses) - return &statuses, resp, body, err + return &statuses, newResponse(resp), body, err } -func (col *StatusesCollection) Get(slug string, params interface{}) (*Status, *http.Response, []byte, error) { +func (col *StatusesCollection) Get(slug string, params interface{}) (*Status, *Response, []byte, error) { var entity Status entityURL := fmt.Sprintf("%v/%v", col.url, slug) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } diff --git a/tags.go b/tags.go index a4dd29c..a867700 100644 --- a/tags.go +++ b/tags.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type Tag struct { @@ -20,31 +19,31 @@ type TagsCollection struct { url string } -func (col *TagsCollection) List(params interface{}) ([]Tag, *http.Response, []byte, error) { +func (col *TagsCollection) List(params interface{}) ([]Tag, *Response, []byte, error) { var tags []Tag resp, body, err := col.client.List(col.url, params, &tags) - return tags, resp, body, err + return tags, newResponse(resp), body, err } -func (col *TagsCollection) Create(new *Tag) (*Tag, *http.Response, []byte, error) { +func (col *TagsCollection) Create(new *Tag) (*Tag, *Response, []byte, error) { var created Tag resp, body, err := col.client.Create(col.url, new, &created) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *TagsCollection) Get(id int, params interface{}) (*Tag, *http.Response, []byte, error) { +func (col *TagsCollection) Get(id int, params interface{}) (*Tag, *Response, []byte, error) { var entity Tag entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } -func (col *TagsCollection) Update(id int, post *Tag) (*Tag, *http.Response, []byte, error) { +func (col *TagsCollection) Update(id int, post *Tag) (*Tag, *Response, []byte, error) { var updated Tag entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, resp, body, err + return &updated, newResponse(resp), body, err } -func (col *TagsCollection) Delete(id int, params interface{}) (*Tag, *http.Response, []byte, error) { +func (col *TagsCollection) Delete(id int, params interface{}) (*Tag, *Response, []byte, error) { var deleted Tag entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } diff --git a/taxonomies.go b/taxonomies.go index e398a69..bf7f8b1 100644 --- a/taxonomies.go +++ b/taxonomies.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type Taxonomy struct { @@ -19,15 +18,15 @@ type TaxonomiesCollection struct { url string } -func (col *TaxonomiesCollection) List(params interface{}) (map[string]Taxonomy, *http.Response, []byte, error) { +func (col *TaxonomiesCollection) List(params interface{}) (map[string]Taxonomy, *Response, []byte, error) { var taxonomies map[string]Taxonomy resp, body, err := col.client.List(col.url, params, &taxonomies) - return taxonomies, resp, body, err + return taxonomies, newResponse(resp), body, err } -func (col *TaxonomiesCollection) Get(slug string, params interface{}) (*Taxonomy, *http.Response, []byte, error) { +func (col *TaxonomiesCollection) Get(slug string, params interface{}) (*Taxonomy, *Response, []byte, error) { var taxonomy Taxonomy entityURL := fmt.Sprintf("%v/%v", col.url, slug) resp, body, err := col.client.Get(entityURL, params, &taxonomy) - return &taxonomy, resp, body, err + return &taxonomy, newResponse(resp), body, err } diff --git a/terms.go b/terms.go index 9c0e343..ae6c97c 100644 --- a/terms.go +++ b/terms.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type Term struct { @@ -20,11 +19,11 @@ type TermsCollection struct { url string } -func (col *TermsCollection) List(taxonomy string, params interface{}) ([]Term, *http.Response, []byte, error) { +func (col *TermsCollection) List(taxonomy string, params interface{}) ([]Term, *Response, []byte, error) { var terms []Term url := fmt.Sprintf("%v/%v", col.url, taxonomy) resp, body, err := col.client.List(url, params, &terms) - return terms, resp, body, err + return terms, newResponse(resp), body, err } func (col *TermsCollection) Tag() *TermsTaxonomyCollection { return &TermsTaxonomyCollection{ @@ -47,31 +46,31 @@ type TermsTaxonomyCollection struct { taxonomyBase string } -func (col *TermsTaxonomyCollection) List(params interface{}) ([]Term, *http.Response, []byte, error) { +func (col *TermsTaxonomyCollection) List(params interface{}) ([]Term, *Response, []byte, error) { var terms []Term resp, body, err := col.client.List(col.url, params, &terms) - return terms, resp, body, err + return terms, newResponse(resp), body, err } -func (col *TermsTaxonomyCollection) Create(new *Term) (*Term, *http.Response, []byte, error) { +func (col *TermsTaxonomyCollection) Create(new *Term) (*Term, *Response, []byte, error) { var created Term resp, body, err := col.client.Create(col.url, new, &created) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *TermsTaxonomyCollection) Get(id int, params interface{}) (*Term, *http.Response, []byte, error) { +func (col *TermsTaxonomyCollection) Get(id int, params interface{}) (*Term, *Response, []byte, error) { var entity Term entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } -func (col *TermsTaxonomyCollection) Update(id int, post *Term) (*Term, *http.Response, []byte, error) { +func (col *TermsTaxonomyCollection) Update(id int, post *Term) (*Term, *Response, []byte, error) { var updated Term entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, resp, body, err + return &updated, newResponse(resp), body, err } -func (col *TermsTaxonomyCollection) Delete(id int, params interface{}) (*Term, *http.Response, []byte, error) { +func (col *TermsTaxonomyCollection) Delete(id int, params interface{}) (*Term, *Response, []byte, error) { var deleted Term entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } diff --git a/types.go b/types.go index aa628db..930bdb8 100644 --- a/types.go +++ b/types.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type TypeLabels struct { @@ -40,15 +39,15 @@ type TypesCollection struct { url string } -func (col *TypesCollection) List(params interface{}) (*Types, *http.Response, []byte, error) { +func (col *TypesCollection) List(params interface{}) (*Types, *Response, []byte, error) { var types Types resp, body, err := col.client.List(col.url, params, &types) - return &types, resp, body, err + return &types, newResponse(resp), body, err } -func (col *TypesCollection) Get(slug string, params interface{}) (*Type, *http.Response, []byte, error) { +func (col *TypesCollection) Get(slug string, params interface{}) (*Type, *Response, []byte, error) { var entity Type entityURL := fmt.Sprintf("%v/%v", col.url, slug) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } diff --git a/users.go b/users.go index a2cf151..2f70ea6 100644 --- a/users.go +++ b/users.go @@ -2,7 +2,6 @@ package wordpress import ( "fmt" - "net/http" ) type AvatarURLS struct { @@ -37,37 +36,37 @@ type UsersCollection struct { url string } -func (col *UsersCollection) Me(params interface{}) (*User, *http.Response, []byte, error) { +func (col *UsersCollection) Me(params interface{}) (*User, *Response, []byte, error) { url := fmt.Sprintf("%v/me", col.url) var user User resp, body, err := col.client.Get(url, params, &user) - return &user, resp, body, err + return &user, newResponse(resp), body, err } -func (col *UsersCollection) List(params interface{}) ([]User, *http.Response, []byte, error) { +func (col *UsersCollection) List(params interface{}) ([]User, *Response, []byte, error) { var users []User resp, body, err := col.client.List(col.url, params, &users) - return users, resp, body, err + return users, newResponse(resp), body, err } -func (col *UsersCollection) Create(new *User) (*User, *http.Response, []byte, error) { +func (col *UsersCollection) Create(new *User) (*User, *Response, []byte, error) { var created User resp, body, err := col.client.Create(col.url, new, &created) - return &created, resp, body, err + return &created, newResponse(resp), body, err } -func (col *UsersCollection) Get(id int, params interface{}) (*User, *http.Response, []byte, error) { +func (col *UsersCollection) Get(id int, params interface{}) (*User, *Response, []byte, error) { var entity User entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, resp, body, err + return &entity, newResponse(resp), body, err } -func (col *UsersCollection) Update(id int, post *User) (*User, *http.Response, []byte, error) { +func (col *UsersCollection) Update(id int, post *User) (*User, *Response, []byte, error) { var updated User entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, resp, body, err + return &updated, newResponse(resp), body, err } -func (col *UsersCollection) Delete(id int, params interface{}) (*User, *http.Response, []byte, error) { +func (col *UsersCollection) Delete(id int, params interface{}) (*User, *Response, []byte, error) { var deleted User entityURL := fmt.Sprintf("%v/%v", col.url, id) resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, resp, body, err + return &deleted, newResponse(resp), body, err } From d373e1021756e7aa4ee3fa3bac7130bc75648c03 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 4 Feb 2018 00:28:20 -0800 Subject: [PATCH 21/46] Overhaul library to be a bit more stable, add features like paging, use context, overally just make it more like google/go-github --- .gometalinter.json | 13 + README.md | 61 +++- categories.go | 78 +++-- client.go | 612 +++++++++++++++++++++-------------- client_test.go | 22 +- comments.go | 122 ++++--- comments_test.go | 69 ++-- discovery.go | 8 +- endpoints.md | 29 +- media.go | 129 +++++--- media_test.go | 68 ++-- meta.go | 53 --- pages.go | 183 ++++++----- pages_meta_test.go | 225 ------------- pages_revisions_test.go | 97 +++--- pages_test.go | 144 ++++----- posts.go | 232 ++++++------- posts_meta_test.go | 225 ------------- posts_revisions_test.go | 95 +++--- posts_terms.go | 78 +++-- posts_terms_category_test.go | 78 ++--- posts_terms_tag_test.go | 78 ++--- posts_terms_test.go | 11 +- posts_test.go | 142 ++++---- revisions.go | 61 ++-- settings.go | 16 +- settings_test.go | 9 +- statuses.go | 26 +- statuses_test.go | 20 +- tags.go | 79 +++-- taxonomies.go | 27 +- taxonomies_test.go | 30 +- terms.go | 90 +++--- terms_category_test.go | 125 +++---- terms_tag_test.go | 79 ++--- terms_test.go | 9 +- time.go | 14 +- types.go | 27 +- types_test.go | 20 +- users.go | 67 ++-- users_test.go | 90 +++--- utils.go | 53 --- 42 files changed, 1693 insertions(+), 2001 deletions(-) create mode 100644 .gometalinter.json delete mode 100644 meta.go delete mode 100644 pages_meta_test.go delete mode 100644 posts_meta_test.go diff --git a/.gometalinter.json b/.gometalinter.json new file mode 100644 index 0000000..cd32833 --- /dev/null +++ b/.gometalinter.json @@ -0,0 +1,13 @@ +{ + "Disable": [ + "aligncheck", + "gocyclo", + "maligned" + ], + "Deadline": "5m", + "Sort": [ + "path", + "linter" + ], + "Vendor": true +} diff --git a/README.md b/README.md index 4aa0e09..9cc0e46 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # go-wp-api -Golang client library for WP-API (Wordpress REST API) +[![GoDoc](https://godoc.org/github.com/robbiet480/go-wordpress?status.svg)](https://godoc.org/github.com/robbiet480/go-wordpress) + +A Go client library for the [Wordpress REST API](https://developer.wordpress.org/rest-api/) ## Installation @@ -24,23 +26,23 @@ func main() { // create wp-api client client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json` + BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json/` Username: USER, Password: PASSWORD, - }) + }, nil) + + ctx := context.Background() // for eg, to get current user (GET /users/me) - currentUser, resp, body, _ := client.Users().Me() - if resp.StatusCode != http.StatusOK { + currentUser, resp, _ := client.Users.Me(ctx) + if resp != nil && resp.StatusCode != http.StatusOK { // handle error } - // `body` will contain raw JSON body in []bytes - // Or you can use your own structs (for custom endpoints, for example) - // Below is the equivalent of `client.Posts().Get(100, nil)` + // Below is the equivalent of `client.Posts.Get(100, nil)` var obj MyCustomPostStruct - resp, body, err := client.Get("/posts/100", nil, &obj) + resp, err := client.Get(ctx, "/posts/100", nil, &obj) // ... log.Println("Current user", currentUser) @@ -51,6 +53,41 @@ For more examples, see package tests. For list of supported/implemented endpoints, see [Endpoints.md](./endpoints.md) +### Pagination ### + +All requests for resource collections (posts, pages, media, revisions, etc.) +support pagination. Pagination options are described in the +`wordpress.ListOptions` struct and passed to the list methods directly or as an +embedded type of a more specific list options struct (for example +`wordpress.PostsListOptions`). Pages information is available via the +`wordpress.Response` struct. + +```go +client := wordpress.NewClient(&wordpress.Options{ + BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json/` + Username: USER, + Password: PASSWORD, +}, nil) + +ctx := context.Background() + +opt := &wordpress.PostsByOrgOptions{ + ListOptions: wordpress.ListOptions{PerPage: 10}, +} +// get all pages of results +var allPosts []*wordpress.Post +for { + posts, resp, err := client.Posts.List(ctx, opt) + if err != nil { + return err + } + allPosts = append(allPosts, posts...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage +} +``` ## Test __Note:__ @@ -59,9 +96,7 @@ Before running the tests, ensure that you have set up your test environment ### Prerequisites - Wordpress 4.x -- WP-API plugin -- WP-API's BasicAuth plugin (for authentication) -- [WP REST API Meta Endpoints plugin](https://github.com/WP-API/wp-api-meta-endpoints) (for Meta endpoints) +- [WP-API's BasicAuth plugin (for authentication)](https://github.com/WP-API/Basic-Auth) ### Setting up test environment - Install prequisites (see above) @@ -76,7 +111,7 @@ Before running the tests, ensure that you have set up your test environment ```bash # Set test enviroment -export WP_API_URL=http://192.168.99.100:32777/wp-json +export WP_API_URL=http://192.168.99.100:32777/wp-json/ export WP_USER= export WP_PASSWD= diff --git a/categories.go b/categories.go index 2aeab17..6dced16 100644 --- a/categories.go +++ b/categories.go @@ -1,9 +1,11 @@ package wordpress import ( + "context" "fmt" ) +// Category represents a WordPress post/page category. type Category struct { ID int `json:"id"` Count int `json:"count"` @@ -15,36 +17,68 @@ type Category struct { Parent int `json:"parent"` } -type CategoriesCollection struct { - client *Client - url string +// CategoriesService provides access to the category related functions in the WordPress REST API. +type CategoriesService service + +// CategoriesListOptions are options that can be passed to List(). +type CategoriesListOptions struct { + Exclude []int `url:"exclude,omitempty"` + Include []int `url:"include,omitempty"` + Parent int `url:"parent,omitempty"` + Post int `url:"post,omitempty"` + Search string `url:"search,omitempty"` + Slug string `url:"slug,omitempty"` + + ListOptions } -func (col *CategoriesCollection) List(params interface{}) ([]Category, *Response, []byte, error) { - var categories []Category - resp, body, err := col.client.List(col.url, params, &categories) - return categories, newResponse(resp), body, err +// List returns a list of categories. +func (c *CategoriesService) List(ctx context.Context, opts *CategoriesListOptions) ([]*Category, *Response, error) { + u, err := addOptions("categories", opts) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + categories := []*Category{} + resp, err := c.client.Do(ctx, req, &categories) + if err != nil { + return nil, resp, err + } + return categories, resp, nil } -func (col *CategoriesCollection) Create(new *Category) (*Category, *Response, []byte, error) { + +// Create creates a new category. +func (c *CategoriesService) Create(ctx context.Context, new *Category) (*Category, *Response, error) { var created Category - resp, body, err := col.client.Create(col.url, new, &created) - return &created, newResponse(resp), body, err + resp, err := c.client.Create(ctx, "categories", new, &created) + return &created, resp, err } -func (col *CategoriesCollection) Get(id int, params interface{}) (*Category, *Response, []byte, error) { + +// Get returns a single category for the given id. +func (c *CategoriesService) Get(ctx context.Context, id int, params interface{}) (*Category, *Response, error) { var entity Category - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "categories", id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } -func (col *CategoriesCollection) Update(id int, post *Category) (*Category, *Response, []byte, error) { + +// Update updates a single category with the given id. +func (c *CategoriesService) Update(ctx context.Context, id int, post *Category) (*Category, *Response, error) { var updated Category - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "categories", id) + resp, err := c.client.Update(ctx, entityURL, post, &updated) + return &updated, resp, err } -func (col *CategoriesCollection) Delete(id int, params interface{}) (*Category, *Response, []byte, error) { + +// Delete removes the category with the given id. +func (c *CategoriesService) Delete(ctx context.Context, id int, params interface{}) (*Category, *Response, error) { var deleted Category - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "categories", id) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) + return &deleted, resp, err } diff --git a/client.go b/client.go index 815028e..6328405 100644 --- a/client.go +++ b/client.go @@ -1,43 +1,45 @@ +// Package wordpress provides a Go client library for the WordPress REST API. package wordpress import ( "bytes" + "context" + "encoding/json" "fmt" + "io" "io/ioutil" "log" "mime/multipart" "net/http" + "net/url" "reflect" "strconv" "strings" "time" - "github.com/parnurzeal/gorequest" + "github.com/google/go-querystring/query" ) const ( - CollectionUsers = "users" - CollectionPosts = "posts" - CollectionPages = "pages" - CollectionMedia = "media" - CollectionMeta = "meta" - CollectionRevisions = "revisions" - CollectionComments = "comments" - CollectionTaxonomies = "taxonomies" - CollectionTerms = "terms" - CollectionStatuses = "statuses" - CollectionTypes = "types" - CollectionSettings = "settings" - CollectionCategories = "categories" - CollectionTags = "tags" + headerTotalRecords = "X-WP-Total" + headerTotalPages = "X-WP-TotalPages" ) -type GeneralError struct { - Code string `json:"code"` - Message string `json:"message"` - Data int `json:"data"` // Unsure if this is consistent +// Error is a generic WordPress error container. +type Error struct { + Response *http.Response // HTTP response that caused this error + Code string `json:"code"` + Message string `json:"message"` + Data int `json:"data"` // Unsure if this is consistent } +func (e *Error) Error() string { + return fmt.Sprintf("%v %v: %d %v", + e.Response.Request.Method, sanitizeURL(e.Response.Request.URL), + e.Response.StatusCode, e.Message) +} + +// Options is a struct containing options that can be passed in during client initialization. type Options struct { BaseAPIURL string Location *time.Location @@ -48,12 +50,50 @@ type Options struct { JWTToken string // TODO: support OAuth authentication + + // User agent used when communicating with the WordPress API. + UserAgent string } +// Client is a struct containing values and methods used for interacting with the WordPress API. type Client struct { - req *gorequest.SuperAgent + client *http.Client options *Options - baseURL string + BaseURL *url.URL + + common service // Reuse a single struct instead of allocating one for each service on the heap. + + Categories *CategoriesService + Comments *CommentsService + Media *MediaService + Pages *PagesService + Posts *PostsService + Settings *SettingsService + Statuses *StatusesService + Tags *TagsService + Taxonomies *TaxonomiesService + Terms *TermsService + Types *TypesService + Users *UsersService +} + +type service struct { + client *Client +} + +// ListOptions specifies the optional parameters to various List methods that +// support pagination. +type ListOptions struct { + // For paginated result sets, page of results to retrieve. + Page int `url:"page,omitempty"` + + // For paginated result sets, the number of results to include per page. + PerPage int `url:"per_page,omitempty"` + + Offset int `url:"offset,omitempty"` + Order string `url:"order,omitempty"` + OrderBy string `url:"orderby,omitempty"` + Context string `url:"context,omitempty"` } // Response is a WordPress REST API response. This wraps the standard http.Response @@ -69,6 +109,14 @@ type Response struct { TotalRecords int TotalPages int + PreviousPage int + NextPage int +} + +// DeleteResponse is used when deleting an object. +type DeleteResponse struct { + Deleted bool `json:"deleted"` + Previous json.RawMessage `json:"previous"` } // newResponse creates a new Response for the provided http.Response. @@ -82,48 +130,27 @@ func newResponse(r *http.Response) *Response { // populatePageValues parses the HTTP Link response headers and populates the // various pagination link values in the Response. func (r *Response) populatePageValues() { - totalRecords, err := strconv.Atoi(r.Header.Get("X-WP-Total")) - if err != nil { - return - } + totalRecords, _ := strconv.Atoi(r.Header.Get(headerTotalRecords)) r.TotalRecords = totalRecords - totalPages, err := strconv.Atoi(r.Header.Get("X-WP-TotalPages")) - if err != nil { - return - } + totalPages, _ := strconv.Atoi(r.Header.Get(headerTotalPages)) r.TotalPages = totalPages -} -// Used to create a new SuperAgent object. -func newHTTPClient() *gorequest.SuperAgent { - client := gorequest.New() - client.Client = &http.Client{Jar: nil} - client.Transport = &http.Transport{ - DisableKeepAlives: true, - } - return client -} + lastPage, _ := strconv.Atoi(r.Request.URL.Query().Get("page")) + + r.PreviousPage = lastPage -func NewClient(options *Options) *Client { - req := newHTTPClient() + r.NextPage = lastPage + 1 - if options.Username != "" && options.Password != "" { - req = req.SetBasicAuth(options.Username, options.Password) - } else if options.JWTToken != "" { - req = req.AppendHeader("Authorization", options.JWTToken) + if r.NextPage >= r.TotalPages { + r.NextPage = 0 } +} - req = req.RedirectPolicy(func(r gorequest.Request, via []gorequest.Request) error { - // perform BasicAuth on each redirect request. - // (requests are cookie-less; so we need to keep re-auth-ing again) - httpReq := http.Request(*r) - httpReq.SetBasicAuth(options.Username, options.Password) - log.Println("REDIRECT", r, options.Username, options.Password) - return nil - }) +// NewClient returns an initalized Client for the given options and httpClient. +func NewClient(options *Options, httpClient *http.Client) *Client { if strings.HasSuffix(options.BaseAPIURL, "/wp/v2") { splitURL := strings.Split(options.BaseAPIURL, "/wp/v2") options.BaseAPIURL = splitURL[0] @@ -133,275 +160,382 @@ func NewClient(options *Options) *Client { Location = options.Location } - return &Client{ - req: req, - options: options, - baseURL: options.BaseAPIURL, + url, _ := url.Parse(options.BaseAPIURL) + + if httpClient == nil { + httpClient = &http.Client{ + Jar: nil, + Transport: &http.Transport{ + DisableKeepAlives: true, + }, + } + + httpClient.CheckRedirect = func(r *http.Request, via []*http.Request) error { + // perform BasicAuth on each redirect request. + // (requests are cookie-less; so we need to keep re-auth-ing again) + r.SetBasicAuth(options.Username, options.Password) + log.Println("REDIRECT", r, options.Username, options.Password) + return nil + } } + + c := &Client{client: httpClient, options: options, BaseURL: url} + c.common.client = c + c.Categories = (*CategoriesService)(&c.common) + c.Comments = (*CommentsService)(&c.common) + c.Media = (*MediaService)(&c.common) + c.Pages = (*PagesService)(&c.common) + c.Posts = (*PostsService)(&c.common) + c.Settings = (*SettingsService)(&c.common) + c.Statuses = (*StatusesService)(&c.common) + c.Tags = (*TagsService)(&c.common) + c.Taxonomies = (*TaxonomiesService)(&c.common) + c.Terms = (*TermsService)(&c.common) + c.Types = (*TypesService)(&c.common) + c.Users = (*UsersService)(&c.common) + return c } -type RootInfo struct { - Authentication map[string]interface{} `json:"authentication"` - Description string `json:"description"` - GMTOffset int `json:"gmt_offset"` - HomeURL string `json:"home"` - Name string `json:"name"` - Namespaces []string `json:"namespaces"` - PermalinkStructure string `json:"permalink_structure"` - TimezoneString string `json:"timezone_string"` - URL string `json:"url"` +// addOptions adds the parameters in opt as URL query parameters to s. opt +// must be a struct whose fields may contain "url" tags. +func addOptions(s string, opt interface{}) (string, error) { + v := reflect.ValueOf(opt) + if v.Kind() == reflect.Ptr && v.IsNil() { + return s, nil + } - Location *time.Location `json:"-"` -} + if v.Kind() == reflect.String { + return fmt.Sprintf("%s?%s", s, opt.(string)), nil + } -func (client *Client) BasicInfo() (*RootInfo, *Response, []byte, error) { - var entity RootInfo - resp, body, err := client.Get(client.baseURL, nil, &entity) + u, err := url.Parse(s) if err != nil { - return &entity, newResponse(resp), body, err + return s, err } - location, locationErr := time.LoadLocation(entity.TimezoneString) - if locationErr != nil { - return &entity, newResponse(resp), body, locationErr + qs, err := query.Values(opt) + if err != nil { + return s, err } - entity.Location = location - return &entity, newResponse(resp), body, err + u.RawQuery = qs.Encode() + return u.String(), nil } -func (client *Client) Users() *UsersCollection { - return &UsersCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionUsers), +// NewRequest creates an API request. A relative URL can be provided in urlStr, +// in which case it is resolved relative to the BaseURL of the Client. +// Relative URLs should always be specified without a preceding slash. If +// specified, the value pointed to by body is JSON encoded and included as the +// request body. +func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { + if !strings.HasSuffix(c.BaseURL.Path, "/") { + return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL) } -} - -func (client *Client) Posts() *PostsCollection { - return &PostsCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionPosts), + if urlStr != "" { + urlStr = fmt.Sprintf("/wp-json/wp/v2/%s", urlStr) } -} - -func (client *Client) Pages() *PagesCollection { - return &PagesCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionPages), + u, err := c.BaseURL.Parse(urlStr) + if err != nil { + return nil, err } -} -func (client *Client) Media() *MediaCollection { - return &MediaCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionMedia), + var buf io.ReadWriter + if body != nil { + buf = new(bytes.Buffer) + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + if encErr := enc.Encode(body); encErr != nil { + return nil, encErr + } } -} -func (client *Client) Comments() *CommentsCollection { - return &CommentsCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionComments), + req, err := http.NewRequest(method, u.String(), buf) + if err != nil { + return nil, err } -} -func (client *Client) Taxonomies() *TaxonomiesCollection { - return &TaxonomiesCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionTaxonomies), + if c.options.Username != "" && c.options.Password != "" { + req.SetBasicAuth(c.options.Username, c.options.Password) + } else if c.options.JWTToken != "" { + req.Header.Add("Authorization", c.options.JWTToken) } -} -func (client *Client) Terms() *TermsCollection { - return &TermsCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionTerms), + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + if c.options.UserAgent != "" { + req.Header.Set("User-Agent", c.options.UserAgent) } + return req, nil } -func (client *Client) Statuses() *StatusesCollection { - return &StatusesCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionStatuses), +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. If v implements the io.Writer +// interface, the raw response body will be written to v, without attempting to +// first decode it. +// +// The provided ctx must be non-nil. If it is canceled or times out, +// ctx.Err() will be returned. +func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) { + req = req.WithContext(ctx) + + resp, err := c.client.Do(req) + if err != nil { + // If we got an error, and the context has been canceled, + // the context's error is probably more useful. + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + // If the error type is *url.Error, sanitize its URL before returning. + if e, ok := err.(*url.Error); ok { + if url, urlErr := url.Parse(e.URL); urlErr == nil { + e.URL = sanitizeURL(url).String() + return nil, e + } + } + + return nil, err } -} -func (client *Client) Types() *TypesCollection { - return &TypesCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionTypes), + // nolint: errcheck + defer func() { + // Drain up to 512 bytes and close the body to let the Transport reuse the connection + io.CopyN(ioutil.Discard, resp.Body, 512) + resp.Body.Close() + }() + + response := newResponse(resp) + + err = CheckResponse(resp) + if err != nil { + // even though there was an error, we still return the response + // in case the caller wants to inspect it further + return response, err + } + + if v != nil { + if w, ok := v.(io.Writer); ok { + if _, copyErr := io.Copy(w, resp.Body); copyErr != nil { + err = copyErr + } + } else { + err = json.NewDecoder(resp.Body).Decode(v) + if err == io.EOF { + err = nil // ignore EOF errors caused by empty response body + } + } } + + return response, err } -func (client *Client) Settings() *SettingsCollection { - return &SettingsCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionSettings), - } +// RootInfo is a struct containing basic and publicly available information about the WordPress REST API. +type RootInfo struct { + Authentication map[string]interface{} `json:"authentication"` + Description string `json:"description"` + GMTOffset int `json:"gmt_offset"` + HomeURL string `json:"home"` + Name string `json:"name"` + Namespaces []string `json:"namespaces"` + PermalinkStructure string `json:"permalink_structure"` + TimezoneString string `json:"timezone_string"` + URL string `json:"url"` + + Location *time.Location `json:"-"` } -func (client *Client) Categories() *CategoriesCollection { - return &CategoriesCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionCategories), +// BasicInfo gets basic and publicly available information about the WordPress REST API. +func (c *Client) BasicInfo(ctx context.Context) (*RootInfo, *Response, error) { + var entity RootInfo + resp, err := c.Get(ctx, c.BaseURL.String(), nil, &entity) + if err != nil { + return &entity, resp, err } -} -func (client *Client) Tags() *TagsCollection { - return &TagsCollection{ - client: client, - url: fmt.Sprintf("%v/wp/v2/%v", client.baseURL, CollectionTags), + location, locationErr := time.LoadLocation(entity.TimezoneString) + if locationErr != nil { + return &entity, resp, locationErr } + entity.Location = location + + return &entity, resp, err } -func (client *Client) List(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { - client.req.TargetType = "json" +// List is a generic function that will return a list of items from the WordPress REST API. +func (c *Client) List(ctx context.Context, url string, params interface{}, result interface{}) (*Response, error) { - req := client.req.Get(url) - if client.options.JWTToken != "" { - req.Set("Authorization", client.options.JWTToken) + u, err := addOptions(url, params) + if err != nil { + return nil, err } - resp, body, errSlice := req.Query(params).EndBytes() - if errSlice != nil && len(errSlice) > 0 { - return nil, body, errSlice[len(errSlice)-1] + req, err := c.NewRequest("GET", u, nil) + if err != nil { + return nil, err } - err := unmarshalResponse(resp, body, result) - _resp := http.Response(*resp) - return &_resp, body, err + + return c.Do(ctx, req, &result) } -func (client *Client) Create(url string, content interface{}, result interface{}) (*http.Response, []byte, error) { - contentVal := unpackInterfacePointer(content) - client.req.TargetType = "json" - req := client.req.Post(url).Send(contentVal) - if client.options.JWTToken != "" { - req.Set("Authorization", client.options.JWTToken) - } - resp, body, errSlice := req.EndBytes() - if errSlice != nil && len(errSlice) > 0 { - return nil, body, errSlice[len(errSlice)-1] +// Create creates a new item on the WordPress REST API. +func (c *Client) Create(ctx context.Context, url string, content interface{}, result interface{}) (*Response, error) { + req, err := c.NewRequest("POST", url, content) + if err != nil { + return nil, err } - err := unmarshalResponse(resp, body, result) - _resp := http.Response(*resp) - return &_resp, body, err -} -func (client *Client) Get(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { - client.req = client.req.AppendHeader("Authorization", client.options.JWTToken) + return c.Do(ctx, req, &result) +} - client.req.TargetType = "json" - req := client.req.Get(url) - if client.options.JWTToken != "" { - req.Set("Authorization", client.options.JWTToken) +// Get returns a single item from the WordPress REST API for the given parameters. +func (c *Client) Get(ctx context.Context, url string, params interface{}, result interface{}) (*Response, error) { + u, err := addOptions(url, params) + if err != nil { + return nil, err } - resp, body, errSlice := req.Query(params).EndBytes() - if errSlice != nil && len(errSlice) > 0 { - return nil, body, errSlice[len(errSlice)-1] + req, err := c.NewRequest("GET", u, nil) + if err != nil { + return nil, err } - err := unmarshalResponse(resp, body, result) - _resp := http.Response(*resp) - return &_resp, body, err + return c.Do(ctx, req, &result) } -func (client *Client) Update(url string, content interface{}, result interface{}) (*http.Response, []byte, error) { +// Update will update an item on the WordPress REST API. +func (c *Client) Update(ctx context.Context, url string, content interface{}, result interface{}) (*Response, error) { + req, err := c.NewRequest("PUT", url, content) + if err != nil { + return nil, err + } - contentVal := unpackInterfacePointer(content) + req.Header.Set("HTTP_X_HTTP_METHOD_OVERRIDE", "PUT") - client.req.TargetType = "json" - req := client.req.Post(url).Send(contentVal) - req.Set("HTTP_X_HTTP_METHOD_OVERRIDE", "PUT") + return c.Do(ctx, req, &result) +} - if client.options.JWTToken != "" { - req.Set("Authorization", client.options.JWTToken) +// Delete will delete an item from the WordPress REST API. +func (c *Client) Delete(ctx context.Context, url string, params interface{}, result interface{}) (*Response, error) { + u, err := addOptions(url, params) + if err != nil { + return nil, err } - resp, body, errSlice := req.EndBytes() - if errSlice != nil && len(errSlice) > 0 { - return nil, body, errSlice[len(errSlice)-1] + req, err := c.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err } - err := unmarshalResponse(resp, body, result) - _resp := http.Response(*resp) - return &_resp, body, err -} -func (client *Client) Delete(url string, params interface{}, result interface{}) (*http.Response, []byte, error) { - client.req.TargetType = "json" - req := client.req.Get(url).Query(params).Query("_method=DELETE") - req.Set("HTTP_X_HTTP_METHOD_OVERRIDE", "DELETE") + req.Header.Set("HTTP_X_HTTP_METHOD_OVERRIDE", "DELETE") - if client.options.JWTToken != "" { - req.Set("Authorization", client.options.JWTToken) - } + if req.URL.Query().Get("force") != "" { + var deleteResp DeleteResponse + + resp, err := c.Do(ctx, req, &deleteResp) + if err != nil { + return resp, err + } + + if deleteResp.Deleted { + if err := json.Unmarshal(deleteResp.Previous, &result); err != nil { + return resp, err + } + } + + return resp, nil - resp, body, errSlice := req.End() - by := []byte(body) - if errSlice != nil && len(errSlice) > 0 { - return resp, by, errSlice[len(errSlice)-1] } - err := unmarshalResponse(resp, by, result) - _resp := http.Response(*resp) - return &_resp, by, err + return c.Do(ctx, req, &result) } -func (client *Client) PostData(url string, content []byte, contentType string, filename string, result interface{}) (*http.Response, []byte, error) { +// PostData allows uploading of binary objects to the WordPress REST API. +func (c *Client) PostData(ctx context.Context, urlStr string, content []byte, contentType string, filename string, result interface{}) (*Response, error) { // gorequest does not support POST-ing raw data // so, we have to manually create a HTTP client - s := client.req.Post(url) - var buf bytes.Buffer w := multipart.NewWriter(&buf) - fileField, _ := w.CreateFormFile("file", filename) - fileField.Write(content) - w.Close() + fileField, fileFieldErr := w.CreateFormFile("file", filename) + if fileFieldErr != nil { + return nil, fileFieldErr + } + if _, writeErr := fileField.Write(content); writeErr != nil { + return nil, writeErr + } + if closeErr := w.Close(); closeErr != nil { + return nil, closeErr + } - req, err := http.NewRequest(s.Method, s.Url, &buf) + if !strings.HasSuffix(c.BaseURL.Path, "/") { + return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL) + } + if urlStr != "" { + urlStr = fmt.Sprintf("/wp-json/wp/v2/%s", urlStr) + } + u, err := c.BaseURL.Parse(urlStr) if err != nil { - return nil, nil, err + return nil, err } - req.Header.Set("Content-Type", w.FormDataContentType()) - req.Header.Set("Content-Disposition", fmt.Sprintf("filename=%v", filename)) + req, err := http.NewRequest("POST", u.String(), &buf) + if err != nil { + return nil, err + } + + if c.options.Username != "" && c.options.Password != "" { + req.SetBasicAuth(c.options.Username, c.options.Password) + } else if c.options.JWTToken != "" { + req.Header.Add("Authorization", c.options.JWTToken) + } - if client.options.JWTToken != "" { - req.Header.Set("Authorization", client.options.JWTToken) + if c.options.UserAgent != "" { + req.Header.Set("User-Agent", c.options.UserAgent) } - // Add basic auth - req.SetBasicAuth(s.BasicAuth.Username, s.BasicAuth.Password) + + req.Header.Set("Content-Type", w.FormDataContentType()) + req.Header.Set("Content-Disposition", fmt.Sprintf("filename=%v", filename)) // Set Transport - s.Client.Transport = s.Transport + // s.Client.Transport = s.Transport // Send request - resp, err := s.Client.Do(req) - if err != nil { - return nil, nil, err - } - defer resp.Body.Close() + return c.Do(ctx, req, &result) +} - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, nil, err +// sanitizeURL redacts the password parameter from the URL which may be +// exposed to the user. +func sanitizeURL(uri *url.URL) *url.URL { + if uri == nil { + return nil } - - err = unmarshalResponse(resp, body, result) - _resp := http.Response(*resp) - return &_resp, body, err + params := uri.Query() + if len(params.Get("password")) > 0 { + params.Set("password", "REDACTED") + uri.RawQuery = params.Encode() + } + return uri } -func unpackInterfacePointer(content interface{}) interface{} { - val := reflect.ValueOf(content) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return nil - } - val = val.Elem() +// CheckResponse checks the API response for errors, and returns them if +// present. A response is considered an error if it has a status code outside +// the 200 range or equal to 202 Accepted. +// API error responses are expected to have either no response +// body, or a JSON response body that maps to ErrorResponse. Any other +// response body will be silently ignored. +func CheckResponse(r *http.Response) error { + if c := r.StatusCode; 200 <= c && c <= 299 { + return nil } - if val.IsValid() { - return val.Interface() + errorResponse := &Error{Response: r} + data, err := ioutil.ReadAll(r.Body) + if err == nil && data != nil { + if jsonErr := json.Unmarshal(data, errorResponse); jsonErr != nil { + return jsonErr + } } - return nil + return errorResponse } diff --git a/client_test.go b/client_test.go index dd22e24..4a426c1 100644 --- a/client_test.go +++ b/client_test.go @@ -1,6 +1,9 @@ package wordpress_test import ( + "context" + "crypto/tls" + "net/http" "os" "testing" @@ -16,7 +19,7 @@ func TestClientNew(t *testing.T) { BaseAPIURL: API_BASE_URL, Username: USER, Password: PASSWORD, - }) + }, nil) if client == nil { t.Fatalf("Client should not be nil") } @@ -27,15 +30,28 @@ Test helper functions */ // initTestClient creates test wordpress client -func initTestClient() *wordpress.Client { +func initTestClient() (*wordpress.Client, context.Context) { if API_BASE_URL == "" { panic("Please set your environment before running the tests") } + httpClient := &http.Client{ + Jar: nil, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // needs to be disabled for Lets Encrypt for whatever reason + DisableKeepAlives: true, + }, + } + + httpClient.CheckRedirect = func(r *http.Request, via []*http.Request) error { + r.SetBasicAuth(USER, PASSWORD) + return nil + } + return wordpress.NewClient(&wordpress.Options{ BaseAPIURL: API_BASE_URL, Username: USER, Password: PASSWORD, - }) + }, httpClient), context.Background() } diff --git a/comments.go b/comments.go index 5dbf40e..b0eace2 100644 --- a/comments.go +++ b/comments.go @@ -1,60 +1,102 @@ package wordpress import ( + "context" "fmt" + "time" ) +// Comment represents a WordPress post comment. type Comment struct { - ID int `json:"id,omitempty"` - AvatarURL string `json:"avatar_url,omitempty"` - AvatarURLs AvatarURLS `json:"avatar_urls,omitempty"` - Author int `json:"author,omitempty"` - AuthorEmail string `json:"author_email,omitempty"` - AuthorIP string `json:"author_ip,omitempty"` - AuthorName string `json:"author_name,omitempty"` - AuthorURL string `json:"author_url,omitempty"` - AuthorUserAgent string `json:"author_user_agent,omitempty"` - Content Content `json:"content,omitempty"` - Date Time `json:"date,omitempty"` - DateGMT Time `json:"date_gmt,omitempty"` - Karma int `json:"karma,omitempty"` - Link string `json:"link,omitempty"` - Parent int `json:"parent,omitempty"` - Post int `json:"post,omitempty"` - Status string `json:"status,omitempty"` - Type string `json:"type,omitempty"` + ID int `json:"id,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + AvatarURLs AvatarURLS `json:"avatar_urls,omitempty"` + Author int `json:"author,omitempty"` + AuthorEmail string `json:"author_email,omitempty"` + AuthorIP string `json:"author_ip,omitempty"` + AuthorName string `json:"author_name,omitempty"` + AuthorURL string `json:"author_url,omitempty"` + AuthorUserAgent string `json:"author_user_agent,omitempty"` + Content RenderedString `json:"content,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` + Karma int `json:"karma,omitempty"` + Link string `json:"link,omitempty"` + Parent int `json:"parent,omitempty"` + Post int `json:"post,omitempty"` + Status string `json:"status,omitempty"` + Type string `json:"type,omitempty"` } -type CommentsCollection struct { - client *Client - url string +// CommentsService provides access to the comment related functions in the WordPress REST API. +type CommentsService service + +// CommentsListOptions are options that can be passed to List(). +type CommentsListOptions struct { + After *time.Time `url:"after,omitempty"` + Author int `url:"author,omitempty"` + AuthorExclude []int `url:"author_exclude,omitempty"` + Before *time.Time `url:"before,omitempty"` + Exclude []int `url:"exclude,omitempty"` + Include []int `url:"include,omitempty"` + Parent []int `url:"parent,omitempty"` + ParentExclude []int `url:"parent_exclude,omitempty"` + Password string `url:"password,omitempty"` + Post int `url:"post,omitempty"` + Search string `url:"search,omitempty"` + Status string `url:"status,omitempty"` + Type string `url:"type,omitempty"` + + ListOptions } -func (col *CommentsCollection) List(params interface{}) ([]Comment, *Response, []byte, error) { - var comments []Comment - resp, body, err := col.client.List(col.url, params, &comments) - return comments, newResponse(resp), body, err +// List returns a list of comments. +func (c *CommentsService) List(ctx context.Context, opts *CommentsListOptions) ([]*Comment, *Response, error) { + u, err := addOptions("comments", opts) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comments := []*Comment{} + resp, err := c.client.Do(ctx, req, &comments) + if err != nil { + return nil, resp, err + } + return comments, resp, nil } -func (col *CommentsCollection) Create(new *Comment) (*Comment, *Response, []byte, error) { + +// Create creates a new comment. +func (c *CommentsService) Create(ctx context.Context, new *Comment) (*Comment, *Response, error) { var created Comment - resp, body, err := col.client.Create(col.url, new, &created) - return &created, newResponse(resp), body, err + resp, err := c.client.Create(ctx, "comments", new, &created) + return &created, resp, err } -func (col *CommentsCollection) Get(id int, params interface{}) (*Comment, *Response, []byte, error) { + +// Get returns a single comment for the given id. +func (c *CommentsService) Get(ctx context.Context, id int, params interface{}) (*Comment, *Response, error) { var entity Comment - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "comments", id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } -func (col *CommentsCollection) Update(id int, post *Comment) (*Comment, *Response, []byte, error) { + +// Update updates a single comment with the given id. +func (c *CommentsService) Update(ctx context.Context, id int, post *Comment) (*Comment, *Response, error) { var updated Comment - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "comments", id) + resp, err := c.client.Update(ctx, entityURL, post, &updated) + return &updated, resp, err } -func (col *CommentsCollection) Delete(id int, params interface{}) (*Comment, *Response, []byte, error) { + +// Delete removes the comment with the given id. +func (c *CommentsService) Delete(ctx context.Context, id int, params interface{}) (*Comment, *Response, error) { var deleted Comment - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "comments", id) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) + return &deleted, resp, err } diff --git a/comments_test.go b/comments_test.go index 65da208..ec9ab24 100644 --- a/comments_test.go +++ b/comments_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "log" "net/http" "testing" @@ -14,7 +15,7 @@ func factoryComment(postID int) wordpress.Comment { Author: 1, Status: wordpress.CommentStatusApproved, AuthorName: "go-wordpress", - Content: wordpress.Content{ + Content: wordpress.RenderedString{ Raw: "Test Comment", Rendered: "

Test Comment

", }, @@ -23,15 +24,12 @@ func factoryComment(postID int) wordpress.Comment { func cleanUpComment(t *testing.T, commentID int) { - wp := initTestClient() - deletedComment, resp, body, err := wp.Comments().Delete(commentID, "force=true") + wp, ctx := initTestClient() + deletedComment, resp, err := wp.Comments.Delete(ctx, commentID, "force=true") if err != nil { t.Errorf("Failed to clean up new comment: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedComment.ID != commentID { @@ -39,41 +37,38 @@ func cleanUpComment(t *testing.T, commentID int) { } } -func getAnyOneComment(t *testing.T, wp *wordpress.Client) *wordpress.Comment { +func getAnyOneComment(t *testing.T, ctx context.Context, wp *wordpress.Client) *wordpress.Comment { - comments, resp, body, err := wp.Comments().List(nil) - if resp.StatusCode != http.StatusOK { + comments, resp, err := wp.Comments.List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(comments) < 1 { log.Print(err) - log.Print(body) log.Print(resp) t.Fatalf("Should not return empty comments") } commentID := comments[0].ID - comment, resp, _, _ := wp.Comments().Get(commentID, "context=edit") - if resp.StatusCode != http.StatusOK { + comment, resp, _ := wp.Comments.Get(ctx, commentID, "context=edit") + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } return comment } func TestCommentsList(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - comments, resp, body, err := wp.Comments().List(nil) + comments, resp, err := wp.Comments.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if comments == nil { t.Errorf("Should not return nil comments") } @@ -83,60 +78,54 @@ func TestCommentsList(t *testing.T) { } func TestCommentsGet_CommentExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - c := getAnyOneComment(t, wp) + c := getAnyOneComment(t, ctx, wp) - comment, resp, body, err := wp.Comments().Get(c.ID, nil) + comment, resp, err := wp.Comments.Get(ctx, c.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if comment == nil { t.Errorf("Should not return nil comments") } } func TestCommentsGet_CommentDoesNotExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - comment, resp, body, err := wp.Comments().Get(-1, nil) + comment, resp, err := wp.Comments.Get(ctx, -1, nil) if err == nil { t.Errorf("Should return error") } - if resp.StatusCode != http.StatusNotFound { + if resp != nil && resp.StatusCode != http.StatusNotFound { t.Errorf("Expected 404 Not Found, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if comment == nil { t.Errorf("Should not return nil comments") } } func TestCommentsCreate(t *testing.T) { - t.Skipf("[TestCommentsCreate] Skipped: there is an issue with creating comments, server returning empty string") - wp := initTestClient() + // t.Skipf("[TestCommentsCreate] Skipped: there is an issue with creating comments, server returning empty string") + wp, ctx := initTestClient() - p := getAnyOnePost(t, wp) + p := getAnyOnePost(t, ctx, wp) c := factoryComment(p.ID) - newComment, resp, body, err := wp.Comments().Create(&c) + newComment, resp, err := wp.Comments.Create(ctx, &c) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusCreated { + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if newComment == nil { t.Errorf("Should not return nil newComment") } diff --git a/discovery.go b/discovery.go index 0bcf929..d1cd377 100644 --- a/discovery.go +++ b/discovery.go @@ -1,12 +1,14 @@ package wordpress import ( + "context" "net/http" "github.com/PuerkitoBio/goquery" "github.com/tomnomnom/linkheader" ) +// DiscoveredAPI is a struct containing details about a discovered WordPress REST API. type DiscoveredAPI struct { BaseURL string DiscoveredURL string @@ -44,15 +46,15 @@ func DiscoverAPI(baseURL string, getRootInfo bool) (*DiscoveredAPI, error) { BaseAPIURL: discovered.DiscoveredURL, } if getRootInfo { - client := NewClient(clientOpts) - info, _, _, basicInfoErr := client.BasicInfo() + client := NewClient(clientOpts, nil) + info, _, basicInfoErr := client.BasicInfo(context.Background()) if basicInfoErr != nil { return nil, basicInfoErr } clientOpts.Location = info.Location discovered.BasicInfo = info } - discovered.Client = NewClient(clientOpts) + discovered.Client = NewClient(clientOpts, nil) return discovered, nil } diff --git a/endpoints.md b/endpoints.md index 45d8f4d..7a1172c 100644 --- a/endpoints.md +++ b/endpoints.md @@ -18,32 +18,6 @@ List of WP-API REST endpoints and implementation status - [ ] `PUT /comments/[id]` (Implemented but untested) - [ ] `DELETE /comments/[id]` (Implemented but untested) -## Meta - -- [x] `GET /[parent_base]/[parent_id]/meta` -- [x] `POST /[parent_base]/[parent_id]/meta` -- [x] `GET /[parent_base]/[parent_id]/meta/[id]` -- [x] `PUT /[parent_base]/[parent_id]/meta/[id]` -- [x] `DELETE /[parent_base]/[parent_id]/meta/[id]` - -`[parent_base] = "posts" | "pages"` - -### Meta Posts - -- [x] `GET /posts/[post_id]/meta` -- [x] `POST /posts/[post_id]/meta` -- [x] `GET /posts/[post_id]/meta/[id]` -- [x] `PUT /posts/[post_id]/meta/[id]` -- [x] `DELETE /posts/[post_id]/meta/[id]` - -### Meta Pages - -- [x] `GET /pages/[post_id]/meta` -- [x] `POST /pages/[post_id]/meta` -- [x] `GET /pages/[post_id]/meta/[id]` -- [x] `PUT /pages/[post_id]/meta/[id]` -- [x] `DELETE /pages/[post_id]/meta/[id]` - ## Post Statuses - [x] `GET /statuses` @@ -154,4 +128,7 @@ List of WP-API REST endpoints and implementation status - [x] `DELETE /users/[id]` - [x] `GET /users/me` +## Settings +- [x] `GET /settings` +- [x] `POST /settings` diff --git a/media.go b/media.go index 4adffb0..6a148ff 100644 --- a/media.go +++ b/media.go @@ -1,9 +1,12 @@ package wordpress import ( + "context" "fmt" + "time" ) +// MediaDetailsSizesItem provides details for a single media item's size. type MediaDetailsSizesItem struct { File string `json:"file,omitempty"` Width int `json:"width,omitempty"` @@ -11,6 +14,8 @@ type MediaDetailsSizesItem struct { MimeType string `json:"mime_type,omitempty"` SourceURL string `json:"source_url,omitempty"` } + +// MediaDetailsSizes provides different sizes of the same media item. type MediaDetailsSizes struct { Thumbnail MediaDetailsSizesItem `json:"thumbnail,omitempty"` Medium MediaDetailsSizesItem `json:"medium,omitempty"` @@ -18,6 +23,8 @@ type MediaDetailsSizes struct { SiteLogo MediaDetailsSizesItem `json:"site-logo,omitempty"` Full MediaDetailsSizesItem `json:"full,omitempty"` } + +// MediaDetails describes specific details about media. type MediaDetails struct { Raw string `json:"raw,omitempty"` Rendered string `json:"rendered,omitempty"` @@ -27,59 +34,101 @@ type MediaDetails struct { Sizes MediaDetailsSizes `json:"sizes,omitempty"` ImageMeta map[string]interface{} `json:"image_meta,omitempty"` } + +// MediaUploadOptions are options that can be passed to Create(). type MediaUploadOptions struct { Filename string ContentType string Data []byte } + +// Media represents a WordPress post media. type Media struct { - ID int `json:"id,omitempty"` - Date Time `json:"date,omitempty"` - DateGMT Time `json:"date_gmt,omitempty"` - GUID GUID `json:"guid,omitempty"` - Link string `json:"link,omitempty"` - Modified Time `json:"modified,omitempty"` - ModifiedGMT Time `json:"modifiedGMT,omitempty"` - Password string `json:"password,omitempty"` - Slug string `json:"slug,omitempty"` - Status string `json:"status,omitempty"` - Type string `json:"type,omitempty"` - Title Title `json:"title,omitempty"` - Author int `json:"author,omitempty"` - MediaStatus string `json:"comment_status,omitempty"` - PingStatus string `json:"ping_status,omitempty"` - AltText string `json:"alt_text,omitempty"` - Caption Caption `json:"caption,omitempty"` - Description Description `json:"description,omitempty"` - MediaType string `json:"media_type,omitempty"` - MediaDetails MediaDetails `json:"media_details,omitempty"` - Post int `json:"post,omitempty"` - SourceURL string `json:"source_url,omitempty"` + ID int `json:"id,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` + GUID RenderedString `json:"guid,omitempty"` + Link string `json:"link,omitempty"` + Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modifiedGMT,omitempty"` + Password string `json:"password,omitempty"` + Slug string `json:"slug,omitempty"` + Status string `json:"status,omitempty"` + Type string `json:"type,omitempty"` + Title RenderedString `json:"title,omitempty"` + Author int `json:"author,omitempty"` + MediaStatus string `json:"media_status,omitempty"` + PingStatus string `json:"ping_status,omitempty"` + AltText string `json:"alt_text,omitempty"` + Caption RenderedString `json:"caption,omitempty"` + Description RenderedString `json:"description,omitempty"` + MediaType string `json:"media_type,omitempty"` + MediaDetails MediaDetails `json:"media_details,omitempty"` + Post int `json:"post,omitempty"` + SourceURL string `json:"source_url,omitempty"` } -type MediaCollection struct { - client *Client - url string + +// MediaService provides access to the media related functions in the WordPress REST API. +type MediaService service + +// MediasListOptions are options that can be passed to List(). +type MediasListOptions struct { + After *time.Time `url:"after,omitempty"` + Author []int `url:"author,omitempty"` + AuthorExclude []int `url:"author_exclude,omitempty"` + Before *time.Time `url:"before,omitempty"` + Exclude []int `url:"exclude,omitempty"` + Include []int `url:"include,omitempty"` + MediaType string `url:"media_type,omitempty"` + MimeType string `url:"mime_type,omitempty"` + Parent []int `url:"parent,omitempty"` + ParentExclude []int `url:"parent_exclude,omitempty"` + Search string `url:"search,omitempty"` + Slug string `url:"slug,omitempty"` + Status string `url:"status,omitempty"` + + ListOptions } -func (col *MediaCollection) List(params interface{}) ([]Media, *Response, []byte, error) { - var media []Media - resp, body, err := col.client.List(col.url, params, &media) - return media, newResponse(resp), body, err +// List returns a list of medias. +func (c *MediaService) List(ctx context.Context, opts *MediasListOptions) ([]*Media, *Response, error) { + u, err := addOptions("media", opts) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + media := []*Media{} + resp, err := c.client.Do(ctx, req, &media) + if err != nil { + return nil, resp, err + } + return media, resp, nil } -func (col *MediaCollection) Create(options *MediaUploadOptions) (*Media, *Response, []byte, error) { + +// Create creates a new media. +func (c *MediaService) Create(ctx context.Context, options *MediaUploadOptions) (*Media, *Response, error) { var created Media - resp, body, err := col.client.PostData(col.url, options.Data, options.ContentType, options.Filename, &created) - return &created, newResponse(resp), body, err + resp, err := c.client.PostData(ctx, "media", options.Data, options.ContentType, options.Filename, &created) + return &created, resp, err } -func (col *MediaCollection) Get(id int, params interface{}) (*Media, *Response, []byte, error) { + +// Get returns a single media item for the given id. +func (c *MediaService) Get(ctx context.Context, id int, params interface{}) (*Media, *Response, error) { var entity Media - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "media", id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } -func (col *MediaCollection) Delete(id int, params interface{}) (*Media, *Response, []byte, error) { + +// Delete removes the media item with the given id. +func (c *MediaService) Delete(ctx context.Context, id int, params interface{}) (*Media, *Response, error) { var deleted Media - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "media", id) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) + return &deleted, resp, err } diff --git a/media_test.go b/media_test.go index 0a98a87..8c05690 100644 --- a/media_test.go +++ b/media_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "io/ioutil" "net/http" "os" @@ -16,10 +17,10 @@ func factoryMediaFileUpload(t *testing.T) *wordpress.MediaUploadOptions { // prepare file to upload file, err := os.Open(path) - defer file.Close() if err != nil { t.Fatalf("Failed to open test media file to upload: %v", err.Error()) } + defer file.Close() fileContents, err := ioutil.ReadAll(file) if err != nil { t.Fatalf("Failed to read test media file to upload: %v", err.Error()) @@ -33,10 +34,11 @@ func factoryMediaFileUpload(t *testing.T) *wordpress.MediaUploadOptions { } return &media } -func getAnyOneMedia(t *testing.T, wp *wordpress.Client) *wordpress.Media { - media, resp, _, _ := wp.Media().List(nil) - if resp.StatusCode != http.StatusOK { +func getAnyOneMedia(t *testing.T, ctx context.Context, wp *wordpress.Client) *wordpress.Media { + + media, resp, _ := wp.Media.List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(media) < 1 { @@ -45,8 +47,8 @@ func getAnyOneMedia(t *testing.T, wp *wordpress.Client) *wordpress.Media { mediaID := media[0].ID - m, resp, _, _ := wp.Media().Get(mediaID, "context=edit") - if resp.StatusCode != http.StatusOK { + m, resp, _ := wp.Media.Get(ctx, mediaID, "context=edit") + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if m == nil { @@ -55,36 +57,32 @@ func getAnyOneMedia(t *testing.T, wp *wordpress.Client) *wordpress.Media { return m } -func cleanUpMedia(t *testing.T, wp *wordpress.Client, mediaID int) { +func cleanUpMedia(t *testing.T, ctx context.Context, wp *wordpress.Client, mediaID int) { - deletedMedia, resp, body, err := wp.Media().Delete(mediaID, "force=true") + deletedMedia, resp, err := wp.Media.Delete(ctx, mediaID, "force=true") if err != nil { t.Errorf("Failed to clean up new media: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedMedia.ID != mediaID { t.Errorf("Deleted comment ID should be the same as newly created comment: %v != %v", deletedMedia.ID, mediaID) } } + func TestMediaList(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - media, resp, body, err := wp.Media().List(nil) + media, resp, err := wp.Media.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if media == nil { t.Errorf("Should not return nil media") } @@ -94,20 +92,18 @@ func TestMediaList(t *testing.T) { } func TestMediaGet_Exists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - m := getAnyOneMedia(t, wp) + m := getAnyOneMedia(t, ctx, wp) - media, resp, body, err := wp.Media().Get(m.ID, nil) + media, resp, err := wp.Media.Get(ctx, m.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if media == nil { t.Errorf("Should not return nil media") } @@ -115,18 +111,16 @@ func TestMediaGet_Exists(t *testing.T) { } func TestMediaGet_DoesNotExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - media, resp, body, err := wp.Media().Get(-1, nil) + media, resp, err := wp.Media.Get(ctx, -1, nil) if err == nil { t.Errorf("Should return error") } - if resp.StatusCode != http.StatusNotFound { + if resp != nil && resp.StatusCode != http.StatusNotFound { t.Errorf("Expected 404 Not Found, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if media == nil { t.Errorf("Should not return nil media") } @@ -135,22 +129,20 @@ func TestMediaGet_DoesNotExists(t *testing.T) { func TestMediaCreate(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() media := factoryMediaFileUpload(t) - newMedia, resp, body, err := wp.Media().Create(media) + newMedia, resp, err := wp.Media.Create(ctx, media) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusCreated { + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if newMedia == nil { t.Errorf("Should not return nil newMedia") } - cleanUpMedia(t, wp, newMedia.ID) + cleanUpMedia(t, ctx, wp, newMedia.ID) } diff --git a/meta.go b/meta.go deleted file mode 100644 index 5330906..0000000 --- a/meta.go +++ /dev/null @@ -1,53 +0,0 @@ -package wordpress - -import ( - "fmt" - "log" -) - -type Meta struct { - ID int `json:"id,omitempty"` - Key string `json:"key,omitempty"` - Value string `json:"value,omitempty"` -} - -type MetaDeletedResponse struct { - Message string `json:"message,omitempty"` -} - -type MetaCollection struct { - client *Client - url string - parent interface{} - parentType string -} - -func (col *MetaCollection) List(params interface{}) ([]Meta, *Response, []byte, error) { - var meta []Meta - resp, body, err := col.client.List(col.url, params, &meta) - return meta, newResponse(resp), body, err -} -func (col *MetaCollection) Create(new *Meta) (*Meta, *Response, []byte, error) { - var created Meta - resp, body, err := col.client.Create(col.url, new, &created) - return &created, newResponse(resp), body, err -} -func (col *MetaCollection) Get(id int, params interface{}) (*Meta, *Response, []byte, error) { - var meta Meta - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &meta) - return &meta, newResponse(resp), body, err -} -func (col *MetaCollection) Update(id int, meta *Meta) (*Meta, *Response, []byte, error) { - var updated Meta - entityURL := fmt.Sprintf("%v/%v", col.url, id) - log.Println("URL", entityURL) - resp, body, err := col.client.Update(entityURL, meta, &updated) - return &updated, newResponse(resp), body, err -} -func (col *MetaCollection) Delete(id int, params interface{}) (*MetaDeletedResponse, *Response, []byte, error) { - var response MetaDeletedResponse - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, params, &response) - return &response, newResponse(resp), body, err -} diff --git a/pages.go b/pages.go index 513b405..8b00463 100644 --- a/pages.go +++ b/pages.go @@ -1,130 +1,163 @@ package wordpress import ( + "context" "fmt" + "time" ) +// Page represents a WordPress page. type Page struct { - collection *PagesCollection - - ID int `json:"id,omitempty"` - Date Time `json:"date,omitempty"` - DateGMT Time `json:"date_gmt,omitempty"` - GUID GUID `json:"guid,omitempty"` - Link string `json:"link,omitempty"` - Modified Time `json:"modified,omitempty"` - ModifiedGMT Time `json:"modifiedGMT,omitempty"` - Password string `json:"password,omitempty"` - Slug string `json:"slug,omitempty"` - Status string `json:"status,omitempty"` - Type string `json:"type,omitempty"` - Parent int `json:"parent,omitempty"` - Title Title `json:"title,omitempty"` - Content Content `json:"content,omitempty"` - Author int `json:"author,omitempty"` - Excerpt Excerpt `json:"excerpt,omitempty"` - FeaturedImage int `json:"featured_image,omitempty"` - CommentStatus string `json:"comment_status,omitempty"` - PingStatus string `json:"ping_status,omitempty"` - MenuOrder int `json:"menu_order,omitempty"` - Template string `json:"template,omitempty"` + collection *PagesService + + ID int `json:"id,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` + GUID RenderedString `json:"guid,omitempty"` + Link string `json:"link,omitempty"` + Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modifiedGMT,omitempty"` + Password string `json:"password,omitempty"` + Slug string `json:"slug,omitempty"` + Status string `json:"status,omitempty"` + Type string `json:"type,omitempty"` + Parent int `json:"parent,omitempty"` + Title RenderedString `json:"title,omitempty"` + Content RenderedString `json:"content,omitempty"` + Author int `json:"author,omitempty"` + Excerpt RenderedString `json:"excerpt,omitempty"` + FeaturedImage int `json:"featured_image,omitempty"` + CommentStatus string `json:"comment_status,omitempty"` + PingStatus string `json:"ping_status,omitempty"` + MenuOrder int `json:"menu_order,omitempty"` + Template string `json:"template,omitempty"` } -func (entity *Page) setCollection(col *PagesCollection) { - entity.collection = col +func (entity *Page) setService(c *PagesService) { + entity.collection = c } -func (entity *Page) Meta() *MetaCollection { - if entity.collection == nil { - // missing page.collection parent. Probably Page struct was initialized manually. - _warning("Missing parent page collection") - return nil - } - return &MetaCollection{ - client: entity.collection.client, - parent: entity, - parentType: CollectionPages, - url: fmt.Sprintf("%v/%v/%v", entity.collection.url, entity.ID, CollectionMeta), - } -} -func (entity *Page) Revisions() *RevisionsCollection { + +// Revisions gets the revisions of a single page. +func (entity *Page) Revisions() *RevisionsService { if entity.collection == nil { // missing page.collection parent. Probably Page struct was initialized manually, not fetched from API _warning("Missing parent page collection") return nil } - return &RevisionsCollection{ - client: entity.collection.client, + return &RevisionsService{ + service: service(*entity.collection), parent: entity, - parentType: CollectionPages, - url: fmt.Sprintf("%v/%v/%v", entity.collection.url, entity.ID, CollectionRevisions), + parentType: "pages", + url: fmt.Sprintf("%v/%v/%v", "pages", entity.ID, "revisions"), } } -func (entity *Page) Populate(params interface{}) (*Page, *Response, []byte, error) { - return entity.collection.Get(entity.ID, params) +// Populate will fill a manually initialized page with the collection information. +func (entity *Page) Populate(ctx context.Context, params interface{}) (*Page, *Response, error) { + return entity.collection.Get(ctx, entity.ID, params) } -type PagesCollection struct { - client *Client - url string - entityURL string +// PagesService provides access to the page related functions in the WordPress REST API. +type PagesService service + +// PagesListOptions are options that can be passed to List(). +type PagesListOptions struct { + After *time.Time `url:"after,omitempty"` + Author int `url:"author,omitempty"` + AuthorExclude []int `url:"author_exclude,omitempty"` + Before *time.Time `url:"before,omitempty"` + Categories []int `url:"categories,omitempty"` + CategoriesExclude []int `url:"categories_exclude,omitempty"` + Exclude []int `url:"exclude,omitempty"` + Include []int `url:"include,omitempty"` + Search string `url:"search,omitempty"` + Slug string `url:"slug,omitempty"` + Status string `url:"status,omitempty"` + Sticky bool `url:"sticky,omitempty"` + Tags []int `url:"tags,omitempty"` + TagsExclude []int `url:"tags_exclude,omitempty"` + + ListOptions } -func (col *PagesCollection) List(params interface{}) ([]Page, *Response, []byte, error) { - var pages []Page - resp, body, err := col.client.List(col.url, params, &pages) +// List returns a list of pages. +func (c *PagesService) List(ctx context.Context, opts *PagesListOptions) ([]*Page, *Response, error) { + u, err := addOptions("pages", opts) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + pages := []*Page{} + resp, err := c.client.Do(ctx, req, &pages) + if err != nil { + return nil, resp, err + } // set collection object for each entity which has sub-collection for _, p := range pages { - p.setCollection(col) + p.setService(c) } - return pages, newResponse(resp), body, err + return pages, resp, nil } -func (col *PagesCollection) Create(new *Page) (*Page, *Response, []byte, error) { + +// Create creates a new page. +func (c *PagesService) Create(ctx context.Context, new *Page) (*Page, *Response, error) { var created Page - resp, body, err := col.client.Create(col.url, new, &created) + resp, err := c.client.Create(ctx, "pages", new, &created) - created.setCollection(col) + created.setService(c) - return &created, newResponse(resp), body, err + return &created, resp, err } -func (col *PagesCollection) Get(id int, params interface{}) (*Page, *Response, []byte, error) { + +// Get returns a single page for the given id. +func (c *PagesService) Get(ctx context.Context, id int, params interface{}) (*Page, *Response, error) { var entity Page - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) + entityURL := fmt.Sprintf("%v/%v", "pages", id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) // set collection object for each entity which has sub-collection - entity.setCollection(col) + entity.setService(c) - return &entity, newResponse(resp), body, err + return &entity, resp, err } -func (col *PagesCollection) Entity(id int) *Page { + +// Entity returns a basic page for the given id. +func (c *PagesService) Entity(id int) *Page { entity := Page{ - collection: col, + collection: c, ID: id, } return &entity } -func (col *PagesCollection) Update(id int, page *Page) (*Page, *Response, []byte, error) { +// Update updates a single page with the given id. +func (c *PagesService) Update(ctx context.Context, id int, page *Page) (*Page, *Response, error) { var updated Page - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Update(entityURL, page, &updated) + entityURL := fmt.Sprintf("%v/%v", "pages", id) + resp, err := c.client.Update(ctx, entityURL, page, &updated) // set collection object for each entity which has sub-collection - updated.setCollection(col) + updated.setService(c) - return &updated, newResponse(resp), body, err + return &updated, resp, err } -func (col *PagesCollection) Delete(id int, params interface{}) (*Page, *Response, []byte, error) { + +// Delete removes the page with the given id. +func (c *PagesService) Delete(ctx context.Context, id int, params interface{}) (*Page, *Response, error) { var deleted Page - entityURL := fmt.Sprintf("%v/%v", col.url, id) + entityURL := fmt.Sprintf("%v/%v", "pages", id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) // set collection object for each entity which has sub-collection - deleted.setCollection(col) + deleted.setService(c) - return &deleted, newResponse(resp), body, err + return &deleted, resp, err } diff --git a/pages_meta_test.go b/pages_meta_test.go deleted file mode 100644 index 9111a73..0000000 --- a/pages_meta_test.go +++ /dev/null @@ -1,225 +0,0 @@ -package wordpress_test - -import ( - "net/http" - "testing" - - "github.com/robbiet480/go-wordpress" -) - -func cleanUpPageMeta(t *testing.T, page *wordpress.Page, metaId int) { - - // note: Need to pass in `force=true` param in order to delete page meta - deletedMeta, resp, body, err := page.Meta().Delete(metaId, "force=true") - if err != nil { - t.Errorf("Failed to clean up new page: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected 200 OK, got %v", resp.Status) - } - if deletedMeta.Message != "Deleted meta" { - t.Errorf("Unexpected response to deleted meta: %v", deletedMeta.Message) - } - -} - -func TestPagesMeta_InvalidCall(t *testing.T) { - // User is not allowed to call create wordpress.Page object manually to retrieve PageMetaCollection - // A proper API call would inject the right PageMetaCollection, Client and other goodies into a page, - // allowing user to call page.Meta() - invalidPage := wordpress.Page{} - invalidMeta := invalidPage.Meta() - if invalidMeta != nil { - t.Errorf("Expected meta to be nil, %v", invalidMeta) - } -} - -func TestPagesMetaList_NoParams(t *testing.T) { - wp := initTestClient() - - page := getAnyOnePage(t, wp) - - meta, resp, body, err := page.Meta().List(nil) - if err != nil { - t.Errorf("Should not return error: %v", err.Error()) - } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected 200 OK, got %v", resp.Status) - } - if meta == nil { - t.Errorf("Should not return nil meta") - } -} - -func TestPagesMetaCreate(t *testing.T) { - wp := initTestClient() - - // get a page - page := getAnyOnePage(t, wp) - - // create meta for retrieved page - m := wordpress.Meta{ - Key: "testKey", - Value: "testValue", - } - newMeta, resp, body, err := page.Meta().Create(&m) - if err != nil { - t.Errorf("Should not return error: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusCreated { - t.Errorf("Expected 201 Created, got %v", resp.Status) - } - if newMeta == nil { - t.Errorf("newMeta should not be nil") - } - if newMeta.Key != m.Key { - t.Errorf("newMeta.Key should be the same, %v != %v", newMeta.Key, m.Key) - } - if newMeta.Value != m.Value { - t.Errorf("newMeta.Value should be the same, %v != %v", newMeta.Value, m.Key) - } - - // clean up - cleanUpPageMeta(t, page, newMeta.ID) -} - -func TestPagesMetaGet(t *testing.T) { - wp := initTestClient() - - // get a page - page := getAnyOnePage(t, wp) - - // create meta for retrieved page - m := wordpress.Meta{ - Key: "testKey", - Value: "testValue", - } - newMeta, resp, body, err := page.Meta().Create(&m) - if resp.StatusCode != http.StatusCreated { - t.Errorf("Expected 201 Created, got %v", resp.Status) - } - - // get meta by id for retrieved page - metaID := newMeta.ID - meta, resp, body, err := page.Meta().Get(metaID, nil) - if err != nil { - t.Errorf("Failed to get meta: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("meta.Value should be the same, %v != %v", meta.Value, m.Value) - } - if meta.ID != metaID { - t.Errorf("meta.ID should be the same, %v != %v", meta.ID, metaID) - } - if newMeta.Key != m.Key { - t.Errorf("meta.Key should be the same, %v != %v", meta.Key, m.Key) - } - if newMeta.Value != m.Value { - t.Errorf("meta.Value should be the same, %v != %v", meta.Value, m.Value) - } - - // clean up - cleanUpPageMeta(t, page, newMeta.ID) -} - -func TestPagesMetaGet_Lazy(t *testing.T) { - - wp := initTestClient() - - // get a page so we can have a valid Page ID and we can create a test meta to get - page := getAnyOnePage(t, wp) - pageID := page.ID - - // create meta for retrieved page - m := wordpress.Meta{ - Key: "testKey", - Value: "testValue", - } - newMeta, resp, _, _ := page.Meta().Create(&m) - if resp.StatusCode != http.StatusCreated { - t.Errorf("Expected 201 Created, got %v", resp.Status) - } - metaID := newMeta.ID - - // Use Pages().Entity(pageID) to retrieve meta in one API call - lazyMeta, resp, body, err := wp.Pages().Entity(pageID).Meta().Get(metaID, nil) - if err != nil { - t.Errorf("Failed to lazy-get meta: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("meta.Value should be the same, %v != %v", lazyMeta.Value, m.Value) - } - if lazyMeta.ID != metaID { - t.Errorf("meta.ID should be the same, %v != %v", lazyMeta.ID, metaID) - } - if lazyMeta.Key != m.Key { - t.Errorf("meta.Key should be the same, %v != %v", lazyMeta.Key, m.Key) - } - if lazyMeta.Value != m.Value { - t.Errorf("meta.Value should be the same, %v != %v", lazyMeta.Value, m.Value) - } -} - -func TestPagesMetaUpdate(t *testing.T) { - wp := initTestClient() - - // get a page - page := getAnyOnePage(t, wp) - - // create meta for retrieved page - m := wordpress.Meta{ - Key: "testKey", - Value: "testValue", - } - newMeta, resp, body, err := page.Meta().Create(&m) - if resp.StatusCode != http.StatusCreated { - t.Errorf("Expected 201 Created, got %v", resp.Status) - } - - // get meta by id for retrieved page - metaID := newMeta.ID - meta, resp, body, err := page.Meta().Get(metaID, nil) - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected 200 OK, got %v", resp.Status) - } - - // update meta by id for retrieved page - meta.Value = "newTestValue" - updatedMeta, resp, body, err := page.Meta().Update(meta.ID, meta) - if err != nil { - t.Errorf("Failed to update page meta: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected 200 OK, got %v", resp.Status) - } - if updatedMeta.ID != meta.ID { - t.Errorf("updatedMeta.ID should be the same, %v != %v", updatedMeta.ID, meta.ID) - } - if updatedMeta.Key != meta.Key { - t.Errorf("updatedMeta.Key should be the same, %v != %v", updatedMeta.Key, meta.Key) - } - if updatedMeta.Value != meta.Value { - t.Errorf("updatedMeta.Value should be the same, %v != %v", updatedMeta.Value, meta.Value) - } - - // clean up - cleanUpPageMeta(t, page, newMeta.ID) -} diff --git a/pages_revisions_test.go b/pages_revisions_test.go index cdb183f..1128fe7 100644 --- a/pages_revisions_test.go +++ b/pages_revisions_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "fmt" "net/http" "testing" @@ -9,10 +10,10 @@ import ( "github.com/robbiet480/go-wordpress" ) -func getLatestRevisionForPage(t *testing.T, page *wordpress.Page) *wordpress.Revision { +func getLatestRevisionForPage(t *testing.T, ctx context.Context, page *wordpress.Page) *wordpress.Revision { - revisions, resp, _, _ := page.Revisions().List(nil) - if resp.StatusCode != http.StatusOK { + revisions, resp, _ := page.Revisions().List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(revisions) < 1 { @@ -20,8 +21,8 @@ func getLatestRevisionForPage(t *testing.T, page *wordpress.Page) *wordpress.Rev } // get latest revision revisionID := revisions[0].ID - revision, resp, _, _ := page.Revisions().Get(revisionID, nil) - if resp.StatusCode != http.StatusOK { + revision, resp, _ := page.Revisions().Get(ctx, revisionID, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } @@ -29,9 +30,9 @@ func getLatestRevisionForPage(t *testing.T, page *wordpress.Page) *wordpress.Rev } func TestPagesRevisions_InvalidCall(t *testing.T) { - // User is not allowed to call create wordpress.Page object manually to retrieve PageMetaCollection - // A proper API call would inject the right PageMetaCollection, Client and other goodies into a page, - // allowing user to call page.Revisions() + // User is not allowed to call create wordpress.Page object manually to retrieve PageMetaService + // A proper API call would inject the right PageMetaService, Client and other goodies into a page, + // allowing user to call page.Revisions invalidPage := wordpress.Page{} invalidRevisions := invalidPage.Revisions() if invalidRevisions != nil { @@ -40,18 +41,16 @@ func TestPagesRevisions_InvalidCall(t *testing.T) { } func TestPagesRevisionsList(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - page := getAnyOnePage(t, wp) + page := getAnyOnePage(t, ctx, wp) - revisions, resp, body, err := page.Revisions().List(nil) + revisions, resp, err := page.Revisions().List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if revisions == nil { @@ -60,20 +59,18 @@ func TestPagesRevisionsList(t *testing.T) { } func TestPagesRevisionsList_Lazy(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - page := getAnyOnePage(t, wp) + page := getAnyOnePage(t, ctx, wp) pageID := page.ID - // Use Pages().Entity(pageID) to retrieve revisions in one API call - lazyRevisions, resp, body, err := wp.Pages().Entity(pageID).Revisions().List(nil) + // Use Pages.Entity(pageID) to retrieve revisions in one API call + lazyRevisions, resp, err := wp.Pages.Entity(pageID).Revisions().List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if lazyRevisions == nil { @@ -82,21 +79,19 @@ func TestPagesRevisionsList_Lazy(t *testing.T) { } func TestPagesRevisionsGet(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - page := getAnyOnePage(t, wp) - r := getLatestRevisionForPage(t, page) + page := getAnyOnePage(t, ctx, wp) + r := getLatestRevisionForPage(t, ctx, page) revisionID := r.ID - revision, resp, body, err := page.Revisions().Get(revisionID, nil) + revision, resp, err := page.Revisions().Get(ctx, revisionID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if revision == nil { @@ -105,23 +100,21 @@ func TestPagesRevisionsGet(t *testing.T) { } func TestPagesRevisionsGet_Lazy(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - page := getAnyOnePage(t, wp) - r := getLatestRevisionForPage(t, page) + page := getAnyOnePage(t, ctx, wp) + r := getLatestRevisionForPage(t, ctx, page) pageID := page.ID revisionID := r.ID - // Use Pages().Entity(pageID) to retrieve revisions in one API call - lazyRevision, resp, body, err := wp.Pages().Entity(pageID).Revisions().Get(revisionID, nil) + // Use Pages.Entity(pageID) to retrieve revisions in one API call + lazyRevision, resp, err := wp.Pages.Entity(pageID).Revisions().Get(ctx, revisionID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if lazyRevision == nil { @@ -130,9 +123,9 @@ func TestPagesRevisionsGet_Lazy(t *testing.T) { } func TestPagesRevisionsDelete_Lazy(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - page := getAnyOnePage(t, wp) + page := getAnyOnePage(t, ctx, wp) // Edit page to create a new revision // Note: wordpress would only create a new revision if there is an actual change in @@ -143,28 +136,26 @@ func TestPagesRevisionsDelete_Lazy(t *testing.T) { if originalTitle == page.Title.Raw { t.Fatalf("Flawed test, ensure that page content is modified before an update") } - updatedPage, resp, _, _ := wp.Pages().Update(page.ID, page) - if resp.StatusCode != http.StatusOK { + updatedPage, resp, _ := wp.Pages.Update(ctx, page.ID, page) + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } - r := getLatestRevisionForPage(t, updatedPage) + r := getLatestRevisionForPage(t, ctx, updatedPage) pageID := updatedPage.ID revisionID := r.ID - // Use Pages().Entity(pageID) to delete revisions in one API call + // Use Pages.Entity(pageID) to delete revisions in one API call // Note that deleting a revision does NOT reverse the changes made in the revision - response, resp, body, err := wp.Pages().Entity(pageID).Revisions().Delete(revisionID, nil) + response, resp, err := wp.Pages.Entity(pageID).Revisions().Delete(ctx, revisionID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if response == false { - t.Errorf("Should not return false (bool) response") + if response == nil { + t.Errorf("Should not return nil response") } } diff --git a/pages_test.go b/pages_test.go index 23d7aad..eb9667b 100644 --- a/pages_test.go +++ b/pages_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "fmt" "log" "net/http" @@ -11,13 +12,13 @@ import ( func factoryPage() wordpress.Page { return wordpress.Page{ - Title: wordpress.Title{ + Title: wordpress.RenderedString{ Raw: "TestPagesCreate", }, - Content: wordpress.Content{ + Content: wordpress.RenderedString{ Raw: "

HEADER

Paragraph

", }, - Excerpt: wordpress.Excerpt{ + Excerpt: wordpress.RenderedString{ Raw: "

HEADER

Paragraph

", }, Type: wordpress.PostTypePage, @@ -29,15 +30,12 @@ func factoryPage() wordpress.Page { func cleanUpPage(t *testing.T, pageID int) { - wp := initTestClient() - deletedPage, resp, body, err := wp.Pages().Delete(pageID, "force=true") + wp, ctx := initTestClient() + deletedPage, resp, err := wp.Pages.Delete(ctx, pageID, "force=true") if err != nil { t.Errorf("Failed to clean up new page: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedPage.ID != pageID { @@ -45,41 +43,38 @@ func cleanUpPage(t *testing.T, pageID int) { } } -func getAnyOnePage(t *testing.T, wp *wordpress.Client) *wordpress.Page { +func getAnyOnePage(t *testing.T, ctx context.Context, wp *wordpress.Client) *wordpress.Page { - pages, resp, body, err := wp.Pages().List(nil) - if resp.StatusCode != http.StatusOK { + pages, resp, err := wp.Pages.List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(pages) < 1 { log.Print(err) - log.Print(body) log.Print(resp) t.Fatalf("Should not return empty pages") } pageID := pages[0].ID - page, resp, _, _ := wp.Pages().Get(pageID, "context=edit") - if resp.StatusCode != http.StatusOK { + page, resp, _ := wp.Pages.Get(ctx, pageID, "context=edit") + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } return page } func TestPagesList_NoParams(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - pages, resp, body, err := wp.Pages().List(nil) + pages, resp, err := wp.Pages.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if pages == nil { t.Errorf("Should not return nil pages") } @@ -88,81 +83,71 @@ func TestPagesList_NoParams(t *testing.T) { } } func TestPagesList_WithParamsString(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() // assumes that API user authenticated with `edit_pages` - pages, resp, body, err := wp.Pages().List("filter[post_status]=draft") + pages, resp, err := wp.Pages.List(ctx, &wordpress.PagesListOptions{Status: "draft"}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if len(pages) != 0 { t.Errorf("Should return zero draft pages, returned %v", len(pages)) } - pages, resp, body, err = wp.Pages().List("filter[post_status]=publish") + pages, resp, err = wp.Pages.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if len(pages) == 0 { t.Errorf("Should return at least one published pages") } } func TestPagesGet_PageExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - page := getAnyOnePage(t, wp) + page := getAnyOnePage(t, ctx, wp) pageID := page.ID - page, resp, body, err := wp.Pages().Get(pageID, nil) + page, resp, err := wp.Pages.Get(ctx, pageID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if page.ID != pageID { t.Errorf("Returned page should have the same ID as specified in Get(), %v != %v", page.ID, pageID) } } func TestPagesGet_PageDoesNotExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() pageID := -1 - _, resp, body, err := wp.Pages().Get(pageID, nil) + _, resp, err := wp.Pages.Get(ctx, pageID, nil) if err == nil { t.Errorf("Should return error") } - if resp.StatusCode != http.StatusNotFound { + if resp != nil && resp.StatusCode != http.StatusNotFound { t.Errorf("Expected 400 NotFound, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } } func TestPagesGet_Lazy(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - page := getAnyOnePage(t, wp) + page := getAnyOnePage(t, ctx, wp) pageID := page.ID - //The proper way to get lazy-fetch pages. Pages().Entity() won't make any HTTP request - lazyPage := wp.Pages().Entity(pageID) + //The proper way to get lazy-fetch pages. Pages.Entityctx, () won't make any HTTP request + lazyPage := wp.Pages.Entity(pageID) if lazyPage == nil { t.Errorf("lazyPage should not be nil") } @@ -174,16 +159,13 @@ func TestPagesGet_Lazy(t *testing.T) { } // populate Page Entity - page, resp, body, err := lazyPage.Populate(nil) + page, resp, err := lazyPage.Populate(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if page.ID != pageID { t.Errorf("Returned page should have the same ID as specified in Get(), %v != %v", page.ID, pageID) } @@ -193,19 +175,16 @@ func TestPagesGet_Lazy(t *testing.T) { } func TestPagesCreate(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() p := factoryPage() - newPage, resp, body, err := wp.Pages().Create(&p) + newPage, resp, err := wp.Pages.Create(ctx, &p) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusCreated { + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if newPage == nil { t.Errorf("newPage should not be nil") } @@ -227,18 +206,18 @@ func TestPagesCreate(t *testing.T) { } func TestPagesUpdate(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() // create a new page first p := factoryPage() - newPage, resp, _, _ := wp.Pages().Create(&p) - if resp.StatusCode != http.StatusCreated { + newPage, resp, _ := wp.Pages.Create(ctx, &p) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Fatalf("Expected 201 Created, got %v", resp.Status) } // get the page in `edit` context - page, resp, _, _ := wp.Pages().Get(newPage.ID, "context=edit") - if resp.StatusCode != http.StatusOK { + page, resp, _ := wp.Pages.Get(ctx, newPage.ID, "context=edit") + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } @@ -250,16 +229,13 @@ func TestPagesUpdate(t *testing.T) { page.Title.Raw = newTitle // update page - updatePage, resp, body, err := wp.Pages().Update(page.ID, page) + updatePage, resp, err := wp.Pages.Update(ctx, page.ID, page) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if updatePage == nil { t.Errorf("updatePage should not be nil") } @@ -272,28 +248,25 @@ func TestPagesUpdate(t *testing.T) { } func TestPagesDelete_NoParams_MoveToTrash(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() // create a new page first p := factoryPage() - newPage, resp, _, _ := wp.Pages().Create(&p) - if resp.StatusCode != http.StatusCreated { + newPage, resp, _ := wp.Pages.Create(ctx, &p) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } // delete page (move to trash) - deletedPage, resp, body, err := wp.Pages().Delete(newPage.ID, nil) + deletedPage, resp, err := wp.Pages.Delete(ctx, newPage.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if deletedPage == nil { - t.Errorf("updatePage should not be nil") + t.Errorf("deletedPage should not be nil") } if deletedPage.ID != newPage.ID { t.Errorf("Deleted page ID should be the same as created page: %v != %v", deletedPage.ID, newPage.ID) @@ -303,26 +276,23 @@ func TestPagesDelete_NoParams_MoveToTrash(t *testing.T) { cleanUpPage(t, newPage.ID) } func TestPagesDelete_WithParams_DeletePermanently(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() // create a new page first p := factoryPage() - newPage, resp, _, _ := wp.Pages().Create(&p) - if resp.StatusCode != http.StatusCreated { + newPage, resp, _ := wp.Pages.Create(ctx, &p) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } // delete page (delete permanently) - deletedPage, resp, body, err := wp.Pages().Delete(newPage.ID, "force=true") + deletedPage, resp, err := wp.Pages.Delete(ctx, newPage.ID, "force=true") if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if deletedPage == nil { t.Errorf("updatePage should not be nil") } diff --git a/posts.go b/posts.go index 5659cd0..d499238 100644 --- a/posts.go +++ b/posts.go @@ -1,9 +1,12 @@ package wordpress import ( + "context" "fmt" + "time" ) +// Constants for different post values. const ( PostStatusDraft = "draft" PostStatusPending = "pending" @@ -34,169 +37,182 @@ const ( PostFormatChat = "chat" ) -type GUID struct { - Raw string `json:"raw,omitempty"` - Rendered string `json:"rendered,omitempty"` -} -type Title struct { - Raw string `json:"raw,omitempty"` - Rendered string `json:"rendered,omitempty"` -} -type Content struct { - Raw string `json:"raw,omitempty"` - Rendered string `json:"rendered,omitempty"` -} -type Excerpt struct { - Raw string `json:"raw,omitempty"` - Rendered string `json:"rendered,omitempty"` -} - -type Description struct { - Raw string `json:"raw,omitempty"` - Rendered string `json:"rendered,omitempty"` -} - -type Caption struct { +// RenderedString contains a raw and rendered version of a string such as title, content, excerpt, etc. +type RenderedString struct { Raw string `json:"raw,omitempty"` Rendered string `json:"rendered,omitempty"` } +// Post represents a WordPress post. type Post struct { - collection *PostsCollection - - Author int `json:"author,omitempty"` - Categories []int `json:"categories,omitempty"` - CommentStatus string `json:"comment_status,omitempty"` - Content Content `json:"content,omitempty"` - Date Time `json:"date,omitempty"` - DateGMT Time `json:"date_gmt,omitempty"` - Excerpt Excerpt `json:"excerpt,omitempty"` - FeaturedMedia int `json:"featured_media,omitempty"` - Format string `json:"format,omitempty"` - GUID GUID `json:"guid,omitempty"` - ID int `json:"id,omitempty"` - Link string `json:"link,omitempty"` - Modified Time `json:"modified,omitempty"` - ModifiedGMT Time `json:"modified_gmt,omitempty"` - Password string `json:"password,omitempty"` - PingStatus string `json:"ping_status,omitempty"` - Slug string `json:"slug,omitempty"` - Status string `json:"status,omitempty"` - Sticky bool `json:"sticky,omitempty"` - Tags []int `json:"tags,omitempty"` - Template string `json:"template,omitempty"` - Title Title `json:"title,omitempty"` - Type string `json:"type,omitempty"` - WpsSubtitle string `json:"wps_subtitle,omitempty"` -} - -func (entity *Post) setCollection(col *PostsCollection) { - entity.collection = col -} -func (entity *Post) Meta() *MetaCollection { - if entity.collection == nil { - // missing post.collection parent. Probably Post struct was initialized manually. - _warning("Missing parent post collection") - return nil - } - return &MetaCollection{ - client: entity.collection.client, - parent: entity, - parentType: CollectionPosts, - url: fmt.Sprintf("%v/%v/%v", entity.collection.url, entity.ID, CollectionMeta), - } -} -func (entity *Post) Revisions() *RevisionsCollection { + collection *PostsService + + Author int `json:"author,omitempty"` + Categories []int `json:"categories,omitempty"` + CommentStatus string `json:"comment_status,omitempty"` + Content RenderedString `json:"content,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` + Excerpt RenderedString `json:"excerpt,omitempty"` + FeaturedMedia int `json:"featured_media,omitempty"` + Format string `json:"format,omitempty"` + GUID RenderedString `json:"guid,omitempty"` + ID int `json:"id,omitempty"` + Link string `json:"link,omitempty"` + Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modified_gmt,omitempty"` + Password string `json:"password,omitempty"` + PingStatus string `json:"ping_status,omitempty"` + Slug string `json:"slug,omitempty"` + Status string `json:"status,omitempty"` + Sticky bool `json:"sticky,omitempty"` + Subtitle string `json:"wps_subtitle,omitempty"` + Tags []int `json:"tags,omitempty"` + Template string `json:"template,omitempty"` + Title RenderedString `json:"title,omitempty"` + Type string `json:"type,omitempty"` +} + +func (entity *Post) setService(c *PostsService) { + entity.collection = c +} + +// Revisions gets the revisions of a single post. +func (entity *Post) Revisions() *RevisionsService { if entity.collection == nil { // missing post.collection parent. Probably Post struct was initialized manually, not fetched from API _warning("Missing parent post collection") return nil } - return &RevisionsCollection{ - client: entity.collection.client, + return &RevisionsService{ + service: service(*entity.collection), parent: entity, - parentType: CollectionPosts, - url: fmt.Sprintf("%v/%v/%v", entity.collection.url, entity.ID, CollectionRevisions), + parentType: "posts", + url: fmt.Sprintf("%v/%v/%v", "posts", entity.ID, "revisions"), } } -func (entity *Post) Terms() *PostsTermsCollection { + +// Terms gets the terms of a single post. +func (entity *Post) Terms() *PostsTermsService { if entity.collection == nil { // missing post.collection parent. Probably Post struct was initialized manually, not fetched from API _warning("Missing parent post collection") return nil } - return &PostsTermsCollection{ + return &PostsTermsService{ client: entity.collection.client, parent: entity, - parentType: CollectionPosts, - url: fmt.Sprintf("%v/%v/%v", entity.collection.url, entity.ID, CollectionTerms), + parentType: "posts", + url: fmt.Sprintf("%v/%v/%v", "posts", entity.ID, "terms"), } } -func (entity *Post) Populate(params interface{}) (*Post, *Response, []byte, error) { - return entity.collection.Get(entity.ID, params) + +// Populate will fill a manually initialized post with the collection information. +func (entity *Post) Populate(ctx context.Context, params interface{}) (*Post, *Response, error) { + return entity.collection.Get(ctx, entity.ID, params) } -type PostsCollection struct { - client *Client - url string - entityURL string +// PostsService provides access to the post related functions in the WordPress REST API. +type PostsService service + +// PostsListOptions are options that can be passed to List(). +type PostsListOptions struct { + After *time.Time `url:"after,omitempty"` + Author int `url:"author,omitempty"` + AuthorExclude []int `url:"author_exclude,omitempty"` + Before *time.Time `url:"before,omitempty"` + Categories []int `url:"categories,omitempty"` + CategoriesExclude []int `url:"categories_exclude,omitempty"` + Exclude []int `url:"exclude,omitempty"` + Include []int `url:"include,omitempty"` + Search string `url:"search,omitempty"` + Slug string `url:"slug,omitempty"` + Status string `url:"status,omitempty"` + Sticky bool `url:"sticky,omitempty"` + Tags []int `url:"tags,omitempty"` + TagsExclude []int `url:"tags_exclude,omitempty"` + + ListOptions } -func (col *PostsCollection) List(params interface{}) ([]Post, *Response, []byte, error) { - var posts []Post - resp, body, err := col.client.List(col.url, params, &posts) +// List returns a list of posts. +func (c *PostsService) List(ctx context.Context, opts *PostsListOptions) ([]*Post, *Response, error) { + u, err := addOptions("posts", opts) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + posts := []*Post{} + resp, err := c.client.Do(ctx, req, &posts) + if err != nil { + return nil, resp, err + } // set collection object for each entity which has sub-collection for _, p := range posts { - p.setCollection(col) + p.setService(c) } - return posts, newResponse(resp), body, err + return posts, resp, nil } -func (col *PostsCollection) Create(new *Post) (*Post, *Response, []byte, error) { + +// Create creates a new post. +func (c *PostsService) Create(ctx context.Context, newPost *Post) (*Post, *Response, error) { var created Post - resp, body, err := col.client.Create(col.url, new, &created) + resp, err := c.client.Create(ctx, "posts", newPost, &created) - created.setCollection(col) + created.setService(c) - return &created, newResponse(resp), body, err + return &created, resp, err } -func (col *PostsCollection) Get(id int, params interface{}) (*Post, *Response, []byte, error) { + +// Get returns a single post for the given id. +func (c *PostsService) Get(ctx context.Context, id int, params interface{}) (*Post, *Response, error) { var entity Post - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) + entityURL := fmt.Sprintf("%v/%v", "posts", id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) // set collection object for each entity which has sub-collection - entity.setCollection(col) + entity.setService(c) - return &entity, newResponse(resp), body, err + return &entity, resp, err } -func (col *PostsCollection) Entity(id int) *Post { + +// Entity returns a basic post for the given id. +func (c *PostsService) Entity(id int) *Post { entity := Post{ - collection: col, + collection: c, ID: id, } return &entity } -func (col *PostsCollection) Update(id int, post *Post) (*Post, *Response, []byte, error) { +// Update updates a single post with the given id. +func (c *PostsService) Update(ctx context.Context, id int, post *Post) (*Post, *Response, error) { var updated Post - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Update(entityURL, post, &updated) + entityURL := fmt.Sprintf("%v/%v", "posts", id) + resp, err := c.client.Update(ctx, entityURL, post, &updated) // set collection object for each entity which has sub-collection - updated.setCollection(col) + updated.setService(c) - return &updated, newResponse(resp), body, err + return &updated, resp, err } -func (col *PostsCollection) Delete(id int, params interface{}) (*Post, *Response, []byte, error) { + +// Delete removes the post with the given id. +func (c *PostsService) Delete(ctx context.Context, id int, params interface{}) (*Post, *Response, error) { var deleted Post - entityURL := fmt.Sprintf("%v/%v", col.url, id) + entityURL := fmt.Sprintf("%v/%v", "posts", id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) // set collection object for each entity which has sub-collection - deleted.setCollection(col) + deleted.setService(c) - return &deleted, newResponse(resp), body, err + return &deleted, resp, err } diff --git a/posts_meta_test.go b/posts_meta_test.go deleted file mode 100644 index cd05ecd..0000000 --- a/posts_meta_test.go +++ /dev/null @@ -1,225 +0,0 @@ -package wordpress_test - -import ( - "net/http" - "testing" - - "github.com/robbiet480/go-wordpress" -) - -func cleanUpPostMeta(t *testing.T, post *wordpress.Post, metaId int) { - - // note: Need to pass in `force=true` param in order to delete post meta - deletedMeta, resp, body, err := post.Meta().Delete(metaId, "force=true") - if err != nil { - t.Errorf("Failed to clean up new post: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected 200 OK, got %v", resp.Status) - } - if deletedMeta.Message != "Deleted meta" { - t.Errorf("Unexpected response to deleted meta: %v", deletedMeta.Message) - } - -} - -func TestPostsMeta_InvalidCall(t *testing.T) { - // User is not allowed to call create wordpress.Post object manually to retrieve PostMetaCollection - // A proper API call would inject the right PostMetaCollection, Client and other goodies into a post, - // allowing user to call post.Meta() - invalidPost := wordpress.Post{} - invalidMeta := invalidPost.Meta() - if invalidMeta != nil { - t.Errorf("Expected meta to be nil, %v", invalidMeta) - } -} - -func TestPostsMetaList_NoParams(t *testing.T) { - wp := initTestClient() - - post := getAnyOnePost(t, wp) - - meta, resp, body, err := post.Meta().List(nil) - if err != nil { - t.Errorf("Should not return error: %v", err.Error()) - } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected 200 OK, got %v", resp.Status) - } - if meta == nil { - t.Errorf("Should not return nil meta") - } -} - -func TestPostsMetaCreate(t *testing.T) { - wp := initTestClient() - - // get a post - post := getAnyOnePost(t, wp) - - // create meta for retrieved post - m := wordpress.Meta{ - Key: "testKey", - Value: "testValue", - } - newMeta, resp, body, err := post.Meta().Create(&m) - if err != nil { - t.Errorf("Should not return error: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusCreated { - t.Errorf("Expected 201 Created, got %v", resp.Status) - } - if newMeta == nil { - t.Errorf("newMeta should not be nil") - } - if newMeta.Key != m.Key { - t.Errorf("newMeta.Key should be the same, %v != %v", newMeta.Key, m.Key) - } - if newMeta.Value != m.Value { - t.Errorf("newMeta.Value should be the same, %v != %v", newMeta.Value, m.Key) - } - - // clean up - cleanUpPostMeta(t, post, newMeta.ID) -} - -func TestPostsMetaGet(t *testing.T) { - wp := initTestClient() - - // get a post - post := getAnyOnePost(t, wp) - - // create meta for retrieved post - m := wordpress.Meta{ - Key: "testKey", - Value: "testValue", - } - newMeta, resp, body, err := post.Meta().Create(&m) - if resp.StatusCode != http.StatusCreated { - t.Errorf("Expected 201 Created, got %v", resp.Status) - } - - // get meta by id for retrieved post - metaID := newMeta.ID - meta, resp, body, err := post.Meta().Get(metaID, nil) - if err != nil { - t.Errorf("Failed to get meta: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("meta.Value should be the same, %v != %v", meta.Value, m.Value) - } - if meta.ID != metaID { - t.Errorf("meta.ID should be the same, %v != %v", meta.ID, metaID) - } - if newMeta.Key != m.Key { - t.Errorf("meta.Key should be the same, %v != %v", meta.Key, m.Key) - } - if newMeta.Value != m.Value { - t.Errorf("meta.Value should be the same, %v != %v", meta.Value, m.Value) - } - - // clean up - cleanUpPostMeta(t, post, newMeta.ID) -} - -func TestPostsMetaGet_Lazy(t *testing.T) { - - wp := initTestClient() - - // get a post so we can have a valid Post ID and we can create a test meta to get - post := getAnyOnePost(t, wp) - postID := post.ID - - // create meta for retrieved post - m := wordpress.Meta{ - Key: "testKey", - Value: "testValue", - } - newMeta, resp, _, _ := post.Meta().Create(&m) - if resp.StatusCode != http.StatusCreated { - t.Errorf("Expected 201 Created, got %v", resp.Status) - } - metaID := newMeta.ID - - // Use Posts().Entity(postID) to retrieve meta in one API call - lazyMeta, resp, body, err := wp.Posts().Entity(postID).Meta().Get(metaID, nil) - if err != nil { - t.Errorf("Failed to lazy-get meta: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("meta.Value should be the same, %v != %v", lazyMeta.Value, m.Value) - } - if lazyMeta.ID != metaID { - t.Errorf("meta.ID should be the same, %v != %v", lazyMeta.ID, metaID) - } - if lazyMeta.Key != m.Key { - t.Errorf("meta.Key should be the same, %v != %v", lazyMeta.Key, m.Key) - } - if lazyMeta.Value != m.Value { - t.Errorf("meta.Value should be the same, %v != %v", lazyMeta.Value, m.Value) - } -} - -func TestPostsMetaUpdate(t *testing.T) { - wp := initTestClient() - - // get a post - post := getAnyOnePost(t, wp) - - // create meta for retrieved post - m := wordpress.Meta{ - Key: "testKey", - Value: "testValue", - } - newMeta, resp, body, err := post.Meta().Create(&m) - if resp.StatusCode != http.StatusCreated { - t.Errorf("Expected 201 Created, got %v", resp.Status) - } - - // get meta by id for retrieved post - metaID := newMeta.ID - meta, resp, body, err := post.Meta().Get(metaID, nil) - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected 200 OK, got %v", resp.Status) - } - - // update meta by id for retrieved post - meta.Value = "newTestValue" - updatedMeta, resp, body, err := post.Meta().Update(meta.ID, meta) - if err != nil { - t.Errorf("Failed to update post meta: %v", err.Error()) - } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected 200 OK, got %v", resp.Status) - } - if updatedMeta.ID != meta.ID { - t.Errorf("updatedMeta.ID should be the same, %v != %v", updatedMeta.ID, meta.ID) - } - if updatedMeta.Key != meta.Key { - t.Errorf("updatedMeta.Key should be the same, %v != %v", updatedMeta.Key, meta.Key) - } - if updatedMeta.Value != meta.Value { - t.Errorf("updatedMeta.Value should be the same, %v != %v", updatedMeta.Value, meta.Value) - } - - // clean up - cleanUpPostMeta(t, post, newMeta.ID) -} diff --git a/posts_revisions_test.go b/posts_revisions_test.go index 67d5b2c..4e26df3 100644 --- a/posts_revisions_test.go +++ b/posts_revisions_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "fmt" "net/http" "testing" @@ -9,10 +10,10 @@ import ( "github.com/robbiet480/go-wordpress" ) -func getLatestRevisionForPost(t *testing.T, post *wordpress.Post) *wordpress.Revision { +func getLatestRevisionForPost(t *testing.T, ctx context.Context, post *wordpress.Post) *wordpress.Revision { - revisions, resp, _, _ := post.Revisions().List(nil) - if resp.StatusCode != http.StatusOK { + revisions, resp, _ := post.Revisions().List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(revisions) < 1 { @@ -20,8 +21,8 @@ func getLatestRevisionForPost(t *testing.T, post *wordpress.Post) *wordpress.Rev } // get latest revision revisionID := revisions[0].ID - revision, resp, _, _ := post.Revisions().Get(revisionID, nil) - if resp.StatusCode != http.StatusOK { + revision, resp, _ := post.Revisions().Get(ctx, revisionID, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } @@ -29,8 +30,8 @@ func getLatestRevisionForPost(t *testing.T, post *wordpress.Post) *wordpress.Rev } func TestPostsRevisions_InvalidCall(t *testing.T) { - // User is not allowed to call create wordpress.Post object manually to retrieve PostMetaCollection - // A proper API call would inject the right PostMetaCollection, Client and other goodies into a post, + // User is not allowed to call create wordpress.Post object manually to retrieve PostMetaService + // A proper API call would inject the right PostMetaService, Client and other goodies into a post, // allowing user to call post.Revisions() invalidPost := wordpress.Post{} invalidRevisions := invalidPost.Revisions() @@ -40,18 +41,16 @@ func TestPostsRevisions_InvalidCall(t *testing.T) { } func TestPostsRevisionsList(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - post := getAnyOnePost(t, wp) + post := getAnyOnePost(t, ctx, wp) - revisions, resp, body, err := post.Revisions().List(nil) + revisions, resp, err := post.Revisions().List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if revisions == nil { @@ -60,20 +59,18 @@ func TestPostsRevisionsList(t *testing.T) { } func TestPostsRevisionsList_Lazy(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - post := getAnyOnePost(t, wp) + post := getAnyOnePost(t, ctx, wp) postID := post.ID - // Use Posts().Entity(postID) to retrieve revisions in one API call - lazyRevisions, resp, body, err := wp.Posts().Entity(postID).Revisions().List(nil) + // Use Posts.Entity(postID) to retrieve revisions in one API call + lazyRevisions, resp, err := wp.Posts.Entity(postID).Revisions().List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if lazyRevisions == nil { @@ -82,21 +79,19 @@ func TestPostsRevisionsList_Lazy(t *testing.T) { } func TestPostsRevisionsGet(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - post := getAnyOnePost(t, wp) - r := getLatestRevisionForPost(t, post) + post := getAnyOnePost(t, ctx, wp) + r := getLatestRevisionForPost(t, ctx, post) revisionID := r.ID - revision, resp, body, err := post.Revisions().Get(revisionID, nil) + revision, resp, err := post.Revisions().Get(ctx, revisionID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if revision == nil { @@ -105,23 +100,21 @@ func TestPostsRevisionsGet(t *testing.T) { } func TestPostsRevisionsGet_Lazy(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - post := getAnyOnePost(t, wp) - r := getLatestRevisionForPost(t, post) + post := getAnyOnePost(t, ctx, wp) + r := getLatestRevisionForPost(t, ctx, post) postID := post.ID revisionID := r.ID - // Use Posts().Entity(postID) to retrieve revisions in one API call - lazyRevision, resp, body, err := wp.Posts().Entity(postID).Revisions().Get(revisionID, nil) + // Use Posts.Entity(postID) to retrieve revisions in one API call + lazyRevision, resp, err := wp.Posts.Entity(postID).Revisions().Get(ctx, revisionID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if lazyRevision == nil { @@ -130,9 +123,9 @@ func TestPostsRevisionsGet_Lazy(t *testing.T) { } func TestPostsRevisionsDelete_Lazy(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - post := getAnyOnePost(t, wp) + post := getAnyOnePost(t, ctx, wp) // Edit post to create a new revision // Note: wordpress would only create a new revision if there is an actual change in @@ -143,28 +136,26 @@ func TestPostsRevisionsDelete_Lazy(t *testing.T) { if originalTitle == post.Title.Raw { t.Fatalf("Flawed test, ensure that post content is modified before an update") } - updatedPost, resp, _, _ := wp.Posts().Update(post.ID, post) - if resp.StatusCode != http.StatusOK { + updatedPost, resp, _ := wp.Posts.Update(ctx, post.ID, post) + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } - r := getLatestRevisionForPost(t, updatedPost) + r := getLatestRevisionForPost(t, ctx, updatedPost) postID := updatedPost.ID revisionID := r.ID - // Use Posts().Entity(postID) to delete revisions in one API call + // Use Posts.Entity(postID) to delete revisions in one API call // Note that deleting a revision does NOT reverse the changes made in the revision - response, resp, body, err := wp.Posts().Entity(postID).Revisions().Delete(revisionID, nil) + response, resp, err := wp.Posts.Entity(postID).Revisions().Delete(ctx, revisionID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("Should not return nil body") - } - if resp.StatusCode != http.StatusOK { + + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if response == false { - t.Errorf("Should not return false (bool) response") + if response == nil { + t.Errorf("Should not return nil response") } } diff --git a/posts_terms.go b/posts_terms.go index d85a5b0..10d1cff 100644 --- a/posts_terms.go +++ b/posts_terms.go @@ -1,9 +1,11 @@ package wordpress import ( + "context" "fmt" ) +// PostsTerm represents a WordPress post post term. type PostsTerm struct { ID int `json:"id,omitempty"` Count int `json:"integer,omitempty"` @@ -15,60 +17,74 @@ type PostsTerm struct { Parent int `json:"parent,omitempty"` } -type PostsTermsCollection struct { +// PostsTermsService provides access to the post term related functions in the WordPress REST API. +type PostsTermsService struct { client *Client url string parent interface{} parentType string } -func (col *PostsTermsCollection) List(taxonomy string, params interface{}) ([]PostsTerm, *Response, []byte, error) { - var terms []PostsTerm - url := fmt.Sprintf("%v/%v", col.url, taxonomy) - resp, body, err := col.client.List(url, params, &terms) - return terms, newResponse(resp), body, err +// List returns a list of post terms. +func (c *PostsTermsService) List(ctx context.Context, taxonomy string, params interface{}) ([]*PostsTerm, *Response, error) { + var terms []*PostsTerm + url := fmt.Sprintf("%v/%v", c.url, taxonomy) + resp, err := c.client.List(ctx, url, params, &terms) + return terms, resp, err } -func (col *PostsTermsCollection) Tag() *PostsTermsTaxonomyCollection { - return &PostsTermsTaxonomyCollection{ - client: col.client, - url: fmt.Sprintf("%v/tag", col.url), + +// Tag returns the tags of a post. +func (c *PostsTermsService) Tag() *PostsTermsTaxonomyService { + return &PostsTermsTaxonomyService{ + client: c.client, + url: fmt.Sprintf("%v/tag", c.url), taxonomyBase: "tag", } } -func (col *PostsTermsCollection) Category() *PostsTermsTaxonomyCollection { - return &PostsTermsTaxonomyCollection{ - client: col.client, - url: fmt.Sprintf("%v/category", col.url), + +// Category returns the categories of a post. +func (c *PostsTermsService) Category() *PostsTermsTaxonomyService { + return &PostsTermsTaxonomyService{ + client: c.client, + url: fmt.Sprintf("%v/category", c.url), taxonomyBase: "category", } } -type PostsTermsTaxonomyCollection struct { +// PostsTermsTaxonomyService contains data about the post terms taxonomy service +type PostsTermsTaxonomyService struct { client *Client url string taxonomyBase string } -func (col *PostsTermsTaxonomyCollection) List(params interface{}) ([]PostsTerm, *Response, []byte, error) { - var terms []PostsTerm - resp, body, err := col.client.List(col.url, params, &terms) - return terms, newResponse(resp), body, err +// List returns a list of post terms. +func (c *PostsTermsTaxonomyService) List(ctx context.Context, params interface{}) ([]*PostsTerm, *Response, error) { + var terms []*PostsTerm + resp, err := c.client.List(ctx, c.url, params, &terms) + return terms, resp, err } -func (col *PostsTermsTaxonomyCollection) Create(id int) (*PostsTerm, *Response, []byte, error) { + +// Create creates a new post term. +func (c *PostsTermsTaxonomyService) Create(ctx context.Context, id int) (*PostsTerm, *Response, error) { var created PostsTerm - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Create(entityURL, nil, &created) - return &created, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", c.url, id) + resp, err := c.client.Create(ctx, entityURL, nil, &created) + return &created, resp, err } -func (col *PostsTermsTaxonomyCollection) Get(id int, params interface{}) (*PostsTerm, *Response, []byte, error) { + +// Get returns a single post term for the given id. +func (c *PostsTermsTaxonomyService) Get(ctx context.Context, id int, params interface{}) (*PostsTerm, *Response, error) { var entity PostsTerm - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", c.url, id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } -func (col *PostsTermsTaxonomyCollection) Delete(id int, params interface{}) (*PostsTerm, *Response, []byte, error) { + +// Delete removes the post term with the given id. +func (c *PostsTermsTaxonomyService) Delete(ctx context.Context, id int, params interface{}) (*PostsTerm, *Response, error) { var deleted PostsTerm - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", c.url, id) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) + return &deleted, resp, err } diff --git a/posts_terms_category_test.go b/posts_terms_category_test.go index a49d66b..78629d2 100644 --- a/posts_terms_category_test.go +++ b/posts_terms_category_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "net/http" "testing" @@ -9,16 +10,13 @@ import ( func cleanUpPostsTermsCategory(t *testing.T, postID int, id int) { - wp := initTestClient() + wp, ctx := initTestClient() // terms does not support trashing - deletedTerm, resp, body, err := wp.Posts().Entity(postID).Terms().Category().Delete(id, "force=true") + deletedTerm, resp, err := wp.Posts.Entity(postID).Terms().Category().Delete(ctx, id, "force=true") if err != nil { t.Errorf("Failed to clean up new term: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedTerm.ID != id { @@ -26,10 +24,10 @@ func cleanUpPostsTermsCategory(t *testing.T, postID int, id int) { } } -func getAnyOnePostsTermsCategory(t *testing.T, wp *wordpress.Client, postID int) *wordpress.PostsTerm { +func getAnyOnePostsTermsCategory(t *testing.T, ctx context.Context, wp *wordpress.Client, postID int) *wordpress.PostsTerm { - terms, resp, _, _ := wp.Posts().Entity(postID).Terms().Category().List(nil) - if resp.StatusCode != http.StatusOK { + terms, resp, _ := wp.Posts.Entity(postID).Terms().Category().List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(terms) < 1 { @@ -38,8 +36,8 @@ func getAnyOnePostsTermsCategory(t *testing.T, wp *wordpress.Client, postID int) id := terms[0].ID - term, resp, _, _ := wp.Posts().Entity(postID).Terms().Category().Get(id, nil) - if resp.StatusCode != http.StatusOK { + term, resp, _ := wp.Posts.Entity(postID).Terms().Category().Get(ctx, id, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } @@ -47,8 +45,8 @@ func getAnyOnePostsTermsCategory(t *testing.T, wp *wordpress.Client, postID int) } func TestPostsTermsCategory_InvalidCall(t *testing.T) { - // User is not allowed to call create wordpress.Post object manually to retrieve PostsTermsCollection - // A proper API call would inject the right PostsTermsCollection, Client and other goodies into a post, + // User is not allowed to call create wordpress.Post object manually to retrieve PostsTermsService + // A proper API call would inject the right PostsTermsService, Client and other goodies into a post, // allowing user to call post.Terms() invalidPost := wordpress.Post{} invalidTerms := invalidPost.Terms() @@ -59,18 +57,15 @@ func TestPostsTermsCategory_InvalidCall(t *testing.T) { func TestPostsTermsCategoryList(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) postID := post.ID - terms, resp, body, err := wp.Posts().Entity(postID).Terms().Category().List(nil) + terms, resp, err := wp.Posts.Entity(postID).Terms().Category().List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if terms == nil { @@ -81,19 +76,16 @@ func TestPostsTermsCategoryList(t *testing.T) { func TestPostsTermsCategoryGet(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) postID := post.ID - tt := getAnyOnePostsTermsCategory(t, wp, postID) + tt := getAnyOnePostsTermsCategory(t, ctx, wp, postID) - term, resp, body, err := wp.Posts().Entity(postID).Terms().Category().Get(tt.ID, nil) + term, resp, err := wp.Posts.Entity(postID).Terms().Category().Get(ctx, tt.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if term == nil { @@ -105,20 +97,17 @@ func TestPostsTermsCategoryGet(t *testing.T) { func TestPostsTermsCategoryCreate_Existing(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) - tt := getAnyOneTermsCategory(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) + tt := getAnyOneTermsCategory(t, ctx, wp) postID := post.ID termID := tt.ID - term, resp, body, err := wp.Posts().Entity(postID).Terms().Category().Create(termID) + term, resp, err := wp.Posts.Entity(postID).Terms().Category().Create(ctx, termID) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusCreated { + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } if term == nil { @@ -132,15 +121,15 @@ func TestPostsTermsCategoryCreate_Existing(t *testing.T) { func TestPostsTermsCategoryDelete(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) - tt := getAnyOneTermsCategory(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) + tt := getAnyOneTermsCategory(t, ctx, wp) postID := post.ID termID := tt.ID // create category - newTerm, resp, _, _ := wp.Posts().Entity(postID).Terms().Category().Create(termID) - if resp.StatusCode != http.StatusCreated { + newTerm, resp, _ := wp.Posts.Entity(postID).Terms().Category().Create(ctx, termID) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } if newTerm == nil { @@ -149,14 +138,11 @@ func TestPostsTermsCategoryDelete(t *testing.T) { // delete category // Note: Terms does not support trashing; `force=true` is required - deletedTerm, resp, body, err := wp.Posts().Entity(postID).Terms().Category().Delete(newTerm.ID, "force=true") + deletedTerm, resp, err := wp.Posts.Entity(postID).Terms().Category().Delete(ctx, newTerm.ID, "force=true") if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if deletedTerm == nil { diff --git a/posts_terms_tag_test.go b/posts_terms_tag_test.go index ce8ebc8..d7387f1 100644 --- a/posts_terms_tag_test.go +++ b/posts_terms_tag_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "net/http" "testing" @@ -9,16 +10,13 @@ import ( func cleanUpPostsTermsTag(t *testing.T, postID int, id int) { - wp := initTestClient() + wp, ctx := initTestClient() // terms does not support trashing - deletedTerm, resp, body, err := wp.Posts().Entity(postID).Terms().Tag().Delete(id, "force=true") + deletedTerm, resp, err := wp.Posts.Entity(postID).Terms().Tag().Delete(ctx, id, "force=true") if err != nil { t.Errorf("Failed to clean up new term: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedTerm.ID != id { @@ -26,10 +24,10 @@ func cleanUpPostsTermsTag(t *testing.T, postID int, id int) { } } -func getAnyOnePostsTermsTag(t *testing.T, wp *wordpress.Client, postID int) *wordpress.PostsTerm { +func getAnyOnePostsTermsTag(t *testing.T, ctx context.Context, wp *wordpress.Client, postID int) *wordpress.PostsTerm { - terms, resp, _, _ := wp.Posts().Entity(postID).Terms().Tag().List(nil) - if resp.StatusCode != http.StatusOK { + terms, resp, _ := wp.Posts.Entity(postID).Terms().Tag().List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(terms) < 1 { @@ -38,8 +36,8 @@ func getAnyOnePostsTermsTag(t *testing.T, wp *wordpress.Client, postID int) *wor id := terms[0].ID - term, resp, _, _ := wp.Posts().Entity(postID).Terms().Tag().Get(id, nil) - if resp.StatusCode != http.StatusOK { + term, resp, _ := wp.Posts.Entity(postID).Terms().Tag().Get(ctx, id, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } @@ -48,8 +46,8 @@ func getAnyOnePostsTermsTag(t *testing.T, wp *wordpress.Client, postID int) *wor func TestPostsTermsTag_InvalidCall(t *testing.T) { t.Skipf("Not supported anymore") - // User is not allowed to call create wordpress.Post object manually to retrieve PostsTermsCollection - // A proper API call would inject the right PostsTermsCollection, Client and other goodies into a post, + // User is not allowed to call create wordpress.Post object manually to retrieve PostsTermsService + // A proper API call would inject the right PostsTermsService, Client and other goodies into a post, // allowing user to call post.Terms() invalidPost := wordpress.Post{} invalidTerms := invalidPost.Terms() @@ -60,18 +58,15 @@ func TestPostsTermsTag_InvalidCall(t *testing.T) { func TestPostsTermsTagList(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) postID := post.ID - terms, resp, body, err := wp.Posts().Entity(postID).Terms().Tag().List(nil) + terms, resp, err := wp.Posts.Entity(postID).Terms().Tag().List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if terms == nil { @@ -82,19 +77,16 @@ func TestPostsTermsTagList(t *testing.T) { func TestPostsTermsTagGet(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) postID := post.ID - tt := getAnyOnePostsTermsTag(t, wp, postID) + tt := getAnyOnePostsTermsTag(t, ctx, wp, postID) - term, resp, body, err := wp.Posts().Entity(postID).Terms().Tag().Get(tt.ID, nil) + term, resp, err := wp.Posts.Entity(postID).Terms().Tag().Get(ctx, tt.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if term == nil { @@ -106,20 +98,17 @@ func TestPostsTermsTagGet(t *testing.T) { func TestPostsTermsTagCreate(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) - tt := getAnyOneTermsTag(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) + tt := getAnyOneTermsTag(t, ctx, wp) postID := post.ID termID := tt.ID - term, resp, body, err := wp.Posts().Entity(postID).Terms().Tag().Create(termID) + term, resp, err := wp.Posts.Entity(postID).Terms().Tag().Create(ctx, termID) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusCreated { + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } if term == nil { @@ -133,15 +122,15 @@ func TestPostsTermsTagCreate(t *testing.T) { func TestPostsTermsTagDelete(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) - tt := getAnyOneTermsTag(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) + tt := getAnyOneTermsTag(t, ctx, wp) postID := post.ID termID := tt.ID // create tag - newTerm, resp, _, _ := wp.Posts().Entity(postID).Terms().Tag().Create(termID) - if resp.StatusCode != http.StatusCreated { + newTerm, resp, _ := wp.Posts.Entity(postID).Terms().Tag().Create(ctx, termID) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } if newTerm == nil { @@ -150,14 +139,11 @@ func TestPostsTermsTagDelete(t *testing.T) { // delete tag // Note: Terms does not support trashing; `force=true` is required - deletedTerm, resp, body, err := wp.Posts().Entity(postID).Terms().Tag().Delete(newTerm.ID, "force=true") + deletedTerm, resp, err := wp.Posts.Entity(postID).Terms().Tag().Delete(ctx, newTerm.ID, "force=true") if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if deletedTerm == nil { diff --git a/posts_terms_test.go b/posts_terms_test.go index 6043868..6ede655 100644 --- a/posts_terms_test.go +++ b/posts_terms_test.go @@ -7,18 +7,15 @@ import ( func TestPostsTermsList(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - post := getAnyOnePost(t, wp) + wp, ctx := initTestClient() + post := getAnyOnePost(t, ctx, wp) postID := post.ID - terms, resp, body, err := wp.Posts().Entity(postID).Terms().List("tag", nil) + terms, resp, err := wp.Posts.Entity(postID).Terms().List(ctx, "tag", nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if terms == nil { diff --git a/posts_test.go b/posts_test.go index 4dc52d0..252f232 100644 --- a/posts_test.go +++ b/posts_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "fmt" "log" "net/http" @@ -11,13 +12,13 @@ import ( func factoryPost() wordpress.Post { return wordpress.Post{ - Title: wordpress.Title{ + Title: wordpress.RenderedString{ Raw: "TestPostsCreate", }, - Content: wordpress.Content{ + Content: wordpress.RenderedString{ Raw: "

HEADER

Paragraph

", }, - Excerpt: wordpress.Excerpt{ + Excerpt: wordpress.RenderedString{ Raw: "

HEADER

Paragraph

", }, Format: wordpress.PostFormatImage, @@ -30,15 +31,12 @@ func factoryPost() wordpress.Post { func cleanUpPost(t *testing.T, postID int) { - wp := initTestClient() - deletedPost, resp, body, err := wp.Posts().Delete(postID, "force=true") + wp, ctx := initTestClient() + deletedPost, resp, err := wp.Posts.Delete(ctx, postID, "force=true") if err != nil { t.Errorf("Failed to clean up new post: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedPost.ID != postID { @@ -46,41 +44,38 @@ func cleanUpPost(t *testing.T, postID int) { } } -func getAnyOnePost(t *testing.T, wp *wordpress.Client) *wordpress.Post { +func getAnyOnePost(t *testing.T, ctx context.Context, wp *wordpress.Client) *wordpress.Post { - posts, resp, body, err := wp.Posts().List(nil) - if resp.StatusCode != http.StatusOK { + posts, resp, err := wp.Posts.List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(posts) < 1 { log.Print(err) - log.Print(body) log.Print(resp) t.Fatalf("Should not return empty posts") } postID := posts[0].ID - post, resp, _, _ := wp.Posts().Get(postID, "context=edit") - if resp.StatusCode != http.StatusOK { + post, resp, _ := wp.Posts.Get(ctx, postID, "context=edit") + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } return post } func TestPostsList_NoParams(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - posts, resp, body, err := wp.Posts().List(nil) + posts, resp, err := wp.Posts.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if posts == nil { t.Errorf("Should not return nil posts") } @@ -89,81 +84,71 @@ func TestPostsList_NoParams(t *testing.T) { } } func TestPostsList_WithParamsString(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() // assumes that API user authenticated with `edit_posts` - posts, resp, body, err := wp.Posts().List("filter[post_status]=draft") + posts, resp, err := wp.Posts.List(ctx, &wordpress.PostsListOptions{Status: "draft"}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if len(posts) != 0 { t.Errorf("Should return zero draft posts, returned %v", len(posts)) } - posts, resp, body, err = wp.Posts().List("filter[post_status]=publish") + posts, resp, err = wp.Posts.List(ctx, &wordpress.PostsListOptions{Status: "publish"}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if len(posts) == 0 { t.Errorf("Should return at least one published posts") } } func TestPostsGet_PostExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - post := getAnyOnePost(t, wp) + post := getAnyOnePost(t, ctx, wp) postID := post.ID - post, resp, body, err := wp.Posts().Get(postID, nil) + post, resp, err := wp.Posts.Get(ctx, postID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if post.ID != postID { t.Errorf("Returned post should have the same ID as specified in Get(), %v != %v", post.ID, postID) } } func TestPostsGet_PostDoesNotExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() postID := -1 - _, resp, body, err := wp.Posts().Get(postID, nil) + _, resp, err := wp.Posts.Get(ctx, postID, nil) if err == nil { t.Errorf("Should return error") } - if resp.StatusCode != http.StatusNotFound { + if resp != nil && resp.StatusCode != http.StatusNotFound { t.Errorf("Expected 400 NotFound, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } } func TestPostsGet_Lazy(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - post := getAnyOnePost(t, wp) + post := getAnyOnePost(t, ctx, wp) postID := post.ID - //The proper way to get lazy-fetch posts. Posts().Entity() won't make any HTTP request - lazyPost := wp.Posts().Entity(postID) + //The proper way to get lazy-fetch posts. Posts.Entity() won't make any HTTP request + lazyPost := wp.Posts.Entity(postID) if lazyPost == nil { t.Errorf("lazyPost should not be nil") } @@ -175,16 +160,13 @@ func TestPostsGet_Lazy(t *testing.T) { } // populate Post Entity - post, resp, body, err := lazyPost.Populate(nil) + post, resp, err := lazyPost.Populate(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if post.ID != postID { t.Errorf("Returned post should have the same ID as specified in Get(), %v != %v", post.ID, postID) } @@ -194,19 +176,16 @@ func TestPostsGet_Lazy(t *testing.T) { } func TestPostsCreate(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() p := factoryPost() - newPost, resp, body, err := wp.Posts().Create(&p) + newPost, resp, err := wp.Posts.Create(ctx, &p) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusCreated { + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if newPost == nil { t.Errorf("newPost should not be nil") } @@ -231,18 +210,18 @@ func TestPostsCreate(t *testing.T) { } func TestPostsUpdate(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() // create a new post first p := factoryPost() - newPost, resp, _, _ := wp.Posts().Create(&p) - if resp.StatusCode != http.StatusCreated { + newPost, resp, _ := wp.Posts.Create(ctx, &p) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Fatalf("Expected 201 Created, got %v", resp.Status) } // get the post in `edit` context - post, resp, _, _ := wp.Posts().Get(newPost.ID, "context=edit") - if resp.StatusCode != http.StatusOK { + post, resp, _ := wp.Posts.Get(ctx, newPost.ID, "context=edit") + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } @@ -254,16 +233,13 @@ func TestPostsUpdate(t *testing.T) { post.Title.Raw = newTitle // update post - updatePost, resp, body, err := wp.Posts().Update(post.ID, post) + updatePost, resp, err := wp.Posts.Update(ctx, post.ID, post) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if updatePost == nil { t.Errorf("updatePost should not be nil") } @@ -276,26 +252,23 @@ func TestPostsUpdate(t *testing.T) { } func TestPostsDelete_NoParams_MoveToTrash(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() // create a new post first p := factoryPost() - newPost, resp, _, _ := wp.Posts().Create(&p) - if resp.StatusCode != http.StatusCreated { + newPost, resp, _ := wp.Posts.Create(ctx, &p) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } // delete post (move to trash) - deletedPost, resp, body, err := wp.Posts().Delete(newPost.ID, nil) + deletedPost, resp, err := wp.Posts.Delete(ctx, newPost.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if deletedPost == nil { t.Errorf("updatePost should not be nil") } @@ -307,26 +280,23 @@ func TestPostsDelete_NoParams_MoveToTrash(t *testing.T) { cleanUpPost(t, newPost.ID) } func TestPostsDelete_WithParams_DeletePermanently(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() // create a new post first p := factoryPost() - newPost, resp, _, _ := wp.Posts().Create(&p) - if resp.StatusCode != http.StatusCreated { + newPost, resp, _ := wp.Posts.Create(ctx, &p) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } // delete post (delete permanently) - deletedPost, resp, body, err := wp.Posts().Delete(newPost.ID, "force=true") + deletedPost, resp, err := wp.Posts.Delete(ctx, newPost.ID, "force=true") if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } - if body == nil { - t.Errorf("body should not be nil") - } if deletedPost == nil { t.Errorf("updatePost should not be nil") } diff --git a/revisions.go b/revisions.go index 6977098..65110e4 100644 --- a/revisions.go +++ b/revisions.go @@ -1,48 +1,53 @@ package wordpress import ( + "context" "fmt" ) +// Revision represents a WordPress page/post revision. type Revision struct { - ID int `json:"id,omitempty"` - Author string `json:"author,omitempty"` // TODO: File a WP-API bug, why am I getting string instead of int? - Date Time `json:"date,omitempty"` - DateGMT Time `json:"dateGMT,omitempty"` - GUID string `json:"guid,omitempty"` - Modified Time `json:"modified,omitempty"` - ModifiedGMT Time `json:"modifiedGMT,omitempty"` - Parent int `json:"parent,omitempty"` - Slug string `json:"slug,omitempty"` - Title string `json:"title,omitempty"` - Content string `json:"content,omitempty"` - Excerpt string `json:"excerpt,omitempty"` + ID int `json:"id,omitempty"` + Author int `json:"author,omitempty"` + Date Time `json:"date,omitempty"` + DateGMT Time `json:"date_gmt,omitempty"` + GUID RenderedString `json:"guid,omitempty"` + Modified Time `json:"modified,omitempty"` + ModifiedGMT Time `json:"modified_gmt,omitempty"` + Parent int `json:"parent,omitempty"` + Slug string `json:"slug,omitempty"` + Title RenderedString `json:"title,omitempty"` + Content RenderedString `json:"content,omitempty"` + Excerpt RenderedString `json:"excerpt,omitempty"` } -type RevisionsCollection struct { - client *Client +// RevisionsService provides access to the revision related functions in the WordPress REST API. +type RevisionsService struct { + service url string parent interface{} parentType string } -func (col *RevisionsCollection) List(params interface{}) ([]Revision, *Response, []byte, error) { - var revisions []Revision - resp, body, err := col.client.List(col.url, params, &revisions) - return revisions, newResponse(resp), body, err +// List returns a list of revisions. +func (c *RevisionsService) List(ctx context.Context, params interface{}) ([]*Revision, *Response, error) { + var revisions []*Revision + resp, err := c.client.List(ctx, c.url, params, &revisions) + return revisions, resp, err } -func (col *RevisionsCollection) Get(id int, params interface{}) (*Revision, *Response, []byte, error) { +// Get returns a single revision for the given id. +func (c *RevisionsService) Get(ctx context.Context, id int, params interface{}) (*Revision, *Response, error) { var revision Revision - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &revision) - return &revision, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", c.url, id) + resp, err := c.client.Get(ctx, entityURL, params, &revision) + return &revision, resp, err } -// TODO: file an issue for inconsistent response -func (col *RevisionsCollection) Delete(id int, params interface{}) (bool, *Response, []byte, error) { - var response bool - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, "force=true", &response) - return response, newResponse(resp), body, err +// Delete removes the revision with the given id. +func (c *RevisionsService) Delete(ctx context.Context, id int, params interface{}) (*Revision, *Response, error) { + var response Revision + entityURL := fmt.Sprintf("%v/%v", c.url, id) + resp, err := c.client.Delete(ctx, entityURL, "force=true", &response) + return &response, resp, err } diff --git a/settings.go b/settings.go index a422660..377f4c5 100644 --- a/settings.go +++ b/settings.go @@ -1,5 +1,8 @@ package wordpress +import "context" + +// Settings represents a WordPress settings. type Settings struct { Title string `json:"title"` Description string `json:"description"` @@ -18,13 +21,12 @@ type Settings struct { DefaultCommentStatus string `json:"default_comment_status"` } -type SettingsCollection struct { - client *Client - url string -} +// SettingsService provides access to the settings related functions in the WordPress REST API. +type SettingsService service -func (col *SettingsCollection) List() (*Settings, *Response, []byte, error) { +// List returns a list of settingss. +func (c *SettingsService) List(ctx context.Context) (*Settings, *Response, error) { var settings Settings - resp, body, err := col.client.List(col.url, nil, &settings) - return &settings, newResponse(resp), body, err + resp, err := c.client.List(ctx, "settings", nil, &settings) + return &settings, resp, err } diff --git a/settings_test.go b/settings_test.go index c5b7322..393dde4 100644 --- a/settings_test.go +++ b/settings_test.go @@ -6,16 +6,13 @@ import ( ) func TestSettingsList(t *testing.T) { - client := initTestClient() + client, ctx := initTestClient() - settings, resp, body, err := client.Settings().List() + settings, resp, err := client.Settings.List(ctx) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if settings == nil { diff --git a/statuses.go b/statuses.go index 14a7133..7655e62 100644 --- a/statuses.go +++ b/statuses.go @@ -1,9 +1,11 @@ package wordpress import ( + "context" "fmt" ) +// Status represents a WordPress post status. type Status struct { Name string `json:"name,omitempty"` Private bool `json:"private,omitempty"` @@ -13,6 +15,7 @@ type Status struct { Slug string `json:"slug,omitempty"` } +// Statuses describes multiple Statuses. type Statuses struct { Publish Status `json:"publish,omitempty"` Future Status `json:"future,omitempty"` @@ -20,20 +23,21 @@ type Statuses struct { Pending Status `json:"pending,omitempty"` Private Status `json:"private,omitempty"` } -type StatusesCollection struct { - client *Client - url string -} -func (col *StatusesCollection) List(params interface{}) (*Statuses, *Response, []byte, error) { +// StatusesService provides access to the Status related functions in the WordPress REST API. +type StatusesService service + +// List returns a list of statuses. +func (c *StatusesService) List(ctx context.Context, params interface{}) (*Statuses, *Response, error) { var statuses Statuses - resp, body, err := col.client.List(col.url, params, &statuses) - return &statuses, newResponse(resp), body, err + resp, err := c.client.List(ctx, "statuses", params, &statuses) + return &statuses, resp, err } -func (col *StatusesCollection) Get(slug string, params interface{}) (*Status, *Response, []byte, error) { +// Get returns a single status for the given id. +func (c *StatusesService) Get(ctx context.Context, slug string, params interface{}) (*Status, *Response, error) { var entity Status - entityURL := fmt.Sprintf("%v/%v", col.url, slug) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "statuses", slug) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } diff --git a/statuses_test.go b/statuses_test.go index bc16578..8f3fd61 100644 --- a/statuses_test.go +++ b/statuses_test.go @@ -6,36 +6,32 @@ import ( ) func TestStatusesList(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - statuses, resp, body, err := wp.Statuses().List(nil) + statuses, resp, err := wp.Statuses.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if statuses == nil { t.Errorf("Should not return nil statuses") } } func TestStatusesGet(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - status, resp, body, err := wp.Statuses().Get("publish", nil) + status, resp, err := wp.Statuses.Get(ctx, "publish", nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if status == nil { t.Errorf("Should not return nil status") } diff --git a/tags.go b/tags.go index a867700..d7d2111 100644 --- a/tags.go +++ b/tags.go @@ -1,9 +1,11 @@ package wordpress import ( + "context" "fmt" ) +// Tag represents a WordPress page/post tag. type Tag struct { ID int `json:"id,omitempty"` Count int `json:"count,omitempty"` @@ -14,36 +16,69 @@ type Tag struct { Taxonomy string `json:"taxonomy,omitempty"` } -type TagsCollection struct { - client *Client - url string +// TagsService provides access to the Tag related functions in the WordPress REST API. +type TagsService service + +// TagsListOptions are options that can be passed to List(). +type TagsListOptions struct { + Exclude []int `url:"exclude,omitempty"` + HideEmpty bool `url:"hide_empty,omitempty"` + Include []int `url:"include,omitempty"` + Parent int `url:"parent,omitempty"` + Post int `url:"post,omitempty"` + Search string `url:"search,omitempty"` + Slug string `url:"slug,omitempty"` + + ListOptions } -func (col *TagsCollection) List(params interface{}) ([]Tag, *Response, []byte, error) { - var tags []Tag - resp, body, err := col.client.List(col.url, params, &tags) - return tags, newResponse(resp), body, err +// List returns a list of tags. +func (c *TagsService) List(ctx context.Context, opts *TagsListOptions) ([]*Tag, *Response, error) { + u, err := addOptions("tags", opts) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + tags := []*Tag{} + resp, err := c.client.Do(ctx, req, &tags) + if err != nil { + return nil, resp, err + } + return tags, resp, nil } -func (col *TagsCollection) Create(new *Tag) (*Tag, *Response, []byte, error) { + +// Create creates a new tag. +func (c *TagsService) Create(ctx context.Context, new *Tag) (*Tag, *Response, error) { var created Tag - resp, body, err := col.client.Create(col.url, new, &created) - return &created, newResponse(resp), body, err + resp, err := c.client.Create(ctx, "tags", new, &created) + return &created, resp, err } -func (col *TagsCollection) Get(id int, params interface{}) (*Tag, *Response, []byte, error) { + +// Get returns a single tag for the given id. +func (c *TagsService) Get(ctx context.Context, id int, params interface{}) (*Tag, *Response, error) { var entity Tag - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "tags", id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } -func (col *TagsCollection) Update(id int, post *Tag) (*Tag, *Response, []byte, error) { + +// Update updates a single tag with the given id. +func (c *TagsService) Update(ctx context.Context, id int, post *Tag) (*Tag, *Response, error) { var updated Tag - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "tags", id) + resp, err := c.client.Update(ctx, entityURL, post, &updated) + return &updated, resp, err } -func (col *TagsCollection) Delete(id int, params interface{}) (*Tag, *Response, []byte, error) { + +// Delete removes the tag with the given id. +func (c *TagsService) Delete(ctx context.Context, id int, params interface{}) (*Tag, *Response, error) { var deleted Tag - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "tags", id) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) + return &deleted, resp, err } diff --git a/taxonomies.go b/taxonomies.go index bf7f8b1..3c0c362 100644 --- a/taxonomies.go +++ b/taxonomies.go @@ -1,32 +1,35 @@ package wordpress import ( + "context" "fmt" ) +// Taxonomy represents a WordPress taxonomy. type Taxonomy struct { Description string `json:"description,omitempty"` Hierarchical bool `json:"hierarchical,omitempty"` Labels map[string]interface{} `json:"labels,omitempty"` Name string `json:"name,omitempty"` - Slug string `json:"slug,omitempty"` ShowCloud bool `json:"show_cloud,omitempty"` + Slug string `json:"slug,omitempty"` Types []string `json:"types,omitempty"` } -type TaxonomiesCollection struct { - client *Client - url string -} -func (col *TaxonomiesCollection) List(params interface{}) (map[string]Taxonomy, *Response, []byte, error) { +// TaxonomiesService provides access to the Taxonomies related functions in the WordPress REST API. +type TaxonomiesService service + +// List returns a list of taxonomies. +func (c *TaxonomiesService) List(ctx context.Context, params interface{}) (map[string]Taxonomy, *Response, error) { var taxonomies map[string]Taxonomy - resp, body, err := col.client.List(col.url, params, &taxonomies) - return taxonomies, newResponse(resp), body, err + resp, err := c.client.List(ctx, "taxonomies", params, &taxonomies) + return taxonomies, resp, err } -func (col *TaxonomiesCollection) Get(slug string, params interface{}) (*Taxonomy, *Response, []byte, error) { +// Get returns a single taxonomy for the given id. +func (c *TaxonomiesService) Get(ctx context.Context, slug string, params interface{}) (*Taxonomy, *Response, error) { var taxonomy Taxonomy - entityURL := fmt.Sprintf("%v/%v", col.url, slug) - resp, body, err := col.client.Get(entityURL, params, &taxonomy) - return &taxonomy, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "taxonomies", slug) + resp, err := c.client.Get(ctx, entityURL, params, &taxonomy) + return &taxonomy, resp, err } diff --git a/taxonomies_test.go b/taxonomies_test.go index 0f57265..33a2935 100644 --- a/taxonomies_test.go +++ b/taxonomies_test.go @@ -6,18 +6,16 @@ import ( ) func TestTaxonomiesList(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - taxonomies, resp, body, err := wp.Taxonomies().List(nil) + taxonomies, resp, err := wp.Taxonomies.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if taxonomies == nil { t.Errorf("Should not return nil taxonomies") } @@ -27,36 +25,32 @@ func TestTaxonomiesList(t *testing.T) { } func TestTaxonomiesGet_TaxonomyExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - taxonomy, resp, body, err := wp.Taxonomies().Get("post_tag", nil) + taxonomy, resp, err := wp.Taxonomies.Get(ctx, "post_tag", nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if taxonomy == nil { t.Errorf("Should not return nil taxonomies") } } func TestTaxonomiesGet_TaxonomyDoesNotExists(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - taxonomy, resp, body, err := wp.Taxonomies().Get("RANDOM", nil) + taxonomy, resp, err := wp.Taxonomies.Get(ctx, "RANDOM", nil) if err == nil { t.Errorf("Should return error") } - if resp.StatusCode != http.StatusNotFound { + if resp != nil && resp.StatusCode != http.StatusNotFound { t.Errorf("Expected 404 Not Found, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if taxonomy == nil { t.Errorf("Should not return nil taxonomies") } diff --git a/terms.go b/terms.go index ae6c97c..1264239 100644 --- a/terms.go +++ b/terms.go @@ -1,9 +1,11 @@ package wordpress import ( + "context" "fmt" ) +// Term represents a WordPress page/post term. type Term struct { ID int `json:"id,omitempty"` Count int `json:"integer,omitempty"` @@ -14,63 +16,77 @@ type Term struct { Taxonomy string `json:"taxonomy,omitempty"` Parent int `json:"parent,omitempty"` } -type TermsCollection struct { - client *Client - url string -} -func (col *TermsCollection) List(taxonomy string, params interface{}) ([]Term, *Response, []byte, error) { - var terms []Term - url := fmt.Sprintf("%v/%v", col.url, taxonomy) - resp, body, err := col.client.List(url, params, &terms) - return terms, newResponse(resp), body, err +// TermsService provides access to the Terms related functions in the WordPress REST API. +type TermsService service + +// List returns a list of terms. +func (c *TermsService) List(ctx context.Context, taxonomy string, params interface{}) ([]*Term, *Response, error) { + var terms []*Term + url := fmt.Sprintf("%v/%v", "terms", taxonomy) + resp, err := c.client.List(ctx, url, params, &terms) + return terms, resp, err } -func (col *TermsCollection) Tag() *TermsTaxonomyCollection { - return &TermsTaxonomyCollection{ - client: col.client, - url: fmt.Sprintf("%v/tag", col.url), + +// Tag returns the terms taxonomy service configured for tags. +func (c *TermsService) Tag() *TermsTaxonomyService { + return &TermsTaxonomyService{ + client: c.client, + url: fmt.Sprintf("%v/tag", "terms"), taxonomyBase: "tag", } } -func (col *TermsCollection) Category() *TermsTaxonomyCollection { - return &TermsTaxonomyCollection{ - client: col.client, - url: fmt.Sprintf("%v/category", col.url), + +// Category returns the terms taxonomy service configured for categories. +func (c *TermsService) Category() *TermsTaxonomyService { + return &TermsTaxonomyService{ + client: c.client, + url: fmt.Sprintf("%v/category", "terms"), taxonomyBase: "category", } } -type TermsTaxonomyCollection struct { +// TermsTaxonomyService contains information about a taxonomy term. +type TermsTaxonomyService struct { client *Client url string taxonomyBase string } -func (col *TermsTaxonomyCollection) List(params interface{}) ([]Term, *Response, []byte, error) { - var terms []Term - resp, body, err := col.client.List(col.url, params, &terms) - return terms, newResponse(resp), body, err +// List returns a list of terms. +func (c *TermsTaxonomyService) List(ctx context.Context, params interface{}) ([]*Term, *Response, error) { + var terms []*Term + resp, err := c.client.List(ctx, c.url, params, &terms) + return terms, resp, err } -func (col *TermsTaxonomyCollection) Create(new *Term) (*Term, *Response, []byte, error) { + +// Create creates a new term. +func (c *TermsTaxonomyService) Create(ctx context.Context, new *Term) (*Term, *Response, error) { var created Term - resp, body, err := col.client.Create(col.url, new, &created) - return &created, newResponse(resp), body, err + resp, err := c.client.Create(ctx, c.url, new, &created) + return &created, resp, err } -func (col *TermsTaxonomyCollection) Get(id int, params interface{}) (*Term, *Response, []byte, error) { + +// Get returns a single term for the given id. +func (c *TermsTaxonomyService) Get(ctx context.Context, id int, params interface{}) (*Term, *Response, error) { var entity Term - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", c.url, id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } -func (col *TermsTaxonomyCollection) Update(id int, post *Term) (*Term, *Response, []byte, error) { + +// Update updates a single term with the given id. +func (c *TermsTaxonomyService) Update(ctx context.Context, id int, post *Term) (*Term, *Response, error) { var updated Term - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", c.url, id) + resp, err := c.client.Update(ctx, entityURL, post, &updated) + return &updated, resp, err } -func (col *TermsTaxonomyCollection) Delete(id int, params interface{}) (*Term, *Response, []byte, error) { + +// Delete removes the term with the given id. +func (c *TermsTaxonomyService) Delete(ctx context.Context, id int, params interface{}) (*Term, *Response, error) { var deleted Term - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", c.url, id) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) + return &deleted, resp, err } diff --git a/terms_category_test.go b/terms_category_test.go index 68f80c1..fb42269 100644 --- a/terms_category_test.go +++ b/terms_category_test.go @@ -1,7 +1,7 @@ package wordpress_test import ( - "log" + "context" "net/http" "testing" @@ -17,15 +17,12 @@ func factoryTermsCategory() *wordpress.Term { func cleanUpTermsCategory(t *testing.T, id int) { - wp := initTestClient() - deletedTerm, resp, body, err := wp.Terms().Category().Delete(id, nil) + wp, ctx := initTestClient() + deletedTerm, resp, err := wp.Terms.Category().Delete(ctx, id, nil) if err != nil { t.Errorf("Failed to clean up new term: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedTerm.ID != id { @@ -33,10 +30,10 @@ func cleanUpTermsCategory(t *testing.T, id int) { } } -func getAnyOneTermsCategory(t *testing.T, wp *wordpress.Client) *wordpress.Term { +func getAnyOneTermsCategory(t *testing.T, ctx context.Context, wp *wordpress.Client) *wordpress.Term { - terms, resp, _, _ := wp.Terms().Category().List(nil) - if resp.StatusCode != http.StatusOK { + terms, resp, _ := wp.Terms.Category().List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(terms) < 1 { @@ -45,8 +42,8 @@ func getAnyOneTermsCategory(t *testing.T, wp *wordpress.Client) *wordpress.Term id := terms[0].ID - term, resp, _, _ := wp.Terms().Category().Get(id, nil) - if resp.StatusCode != http.StatusOK { + term, resp, _ := wp.Terms.Category().Get(ctx, id, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } @@ -55,16 +52,13 @@ func getAnyOneTermsCategory(t *testing.T, wp *wordpress.Client) *wordpress.Term func TestTermsCategoryList(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() - terms, resp, body, err := wp.Terms().Category().List(nil) + terms, resp, err := wp.Terms.Category().List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if terms == nil { @@ -75,17 +69,14 @@ func TestTermsCategoryList(t *testing.T) { func TestTermsCategoryGet(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - tt := getAnyOneTermsCategory(t, wp) + wp, ctx := initTestClient() + tt := getAnyOneTermsCategory(t, ctx, wp) - term, resp, body, err := wp.Terms().Category().Get(tt.ID, nil) + term, resp, err := wp.Terms.Category().Get(ctx, tt.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if term == nil { @@ -97,19 +88,17 @@ func TestTermsCategoryGet(t *testing.T) { func TestTermsCategoryCreate_New(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() tt := factoryTermsCategory() - term, resp, body, err := wp.Terms().Category().Create(tt) + term, resp, err := wp.Terms.Category().Create(ctx, tt) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) + } if term == nil { t.Errorf("Should not return nil term") @@ -122,19 +111,16 @@ func TestTermsCategoryCreate_New(t *testing.T) { func TestTermsCategoryCreate_Existing(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() tt := factoryTermsCategory() // add category the first time - term, resp, body, err := wp.Terms().Category().Create(tt) + term, resp, err := wp.Terms.Category().Create(ctx, tt) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if term == nil { @@ -142,34 +128,31 @@ func TestTermsCategoryCreate_Existing(t *testing.T) { } // add the same category the second time - duplicateTerm, resp, body, err := wp.Terms().Category().Create(tt) + duplicateTerm, resp, err := wp.Terms.Category().Create(ctx, tt) if err == nil { t.Errorf("Should return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusInternalServerError { + if resp != nil && resp.StatusCode != http.StatusInternalServerError { t.Errorf("Expected 500 Internal Server Erro, got %v", resp.Status) } if duplicateTerm == nil { t.Errorf("Should not return nil duplicateTerm") } - // unmarshall error response - // We expect server to return "term_exists" error code - serverErrors, err := wordpress.UnmarshalServerError(body) - if err != nil { - cleanUpTermsCategory(t, term.ID) - log.Println(string(body)) - t.Fatalf("Unexpected error response from server, unable to unmarshall message %v", err.Error()) - } - if len(serverErrors) != 1 { - t.Error("Expected one error", len(serverErrors)) - } - if serverErrors[0].Code != "term_exists" { - t.Errorf("Unexpected err.code, %v != term_exists", serverErrors[0].Code) - } + // // unmarshall error response + // // We expect server to return "term_exists" error code + // serverErrors, err := wordpress.UnmarshalServerError(body) + // if err != nil { + // cleanUpTermsCategory(t, term.ID) + // log.Println(string(body)) + // t.Fatalf("Unexpected error response from server, unable to unmarshall message %v", err.Error()) + // } + // if len(serverErrors) != 1 { + // t.Error("Expected one error", len(serverErrors)) + // } + // if serverErrors[0].Code != "term_exists" { + // t.Errorf("Unexpected err.code, %v != term_exists", serverErrors[0].Code) + // } // clean up cleanUpTermsCategory(t, term.ID) @@ -179,13 +162,13 @@ func TestTermsCategoryCreate_Existing(t *testing.T) { func TestTermsCategoryDelete(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() tt := factoryTermsCategory() // create category - newTerm, resp, _, _ := wp.Terms().Category().Create(tt) - if resp.StatusCode != http.StatusOK { + newTerm, resp, _ := wp.Terms.Category().Create(ctx, tt) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if newTerm == nil { @@ -193,14 +176,11 @@ func TestTermsCategoryDelete(t *testing.T) { } // delete category - deletedTerm, resp, body, err := wp.Terms().Category().Delete(newTerm.ID, nil) + deletedTerm, resp, err := wp.Terms.Category().Delete(ctx, newTerm.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if deletedTerm == nil { @@ -211,13 +191,13 @@ func TestTermsCategoryDelete(t *testing.T) { func TestTermsCategoryUpdate(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() tt := factoryTermsCategory() // create category - newTerm, resp, _, _ := wp.Terms().Category().Create(tt) - if resp.StatusCode != http.StatusOK { + newTerm, resp, _ := wp.Terms.Category().Create(ctx, tt) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if newTerm == nil { @@ -225,8 +205,8 @@ func TestTermsCategoryUpdate(t *testing.T) { } // get category term - term, resp, _, _ := wp.Terms().Category().Get(newTerm.ID, nil) - if resp.StatusCode != http.StatusOK { + term, resp, _ := wp.Terms.Category().Get(ctx, newTerm.ID, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if term == nil { @@ -241,14 +221,11 @@ func TestTermsCategoryUpdate(t *testing.T) { term.Description = newTermDescription // update - updatedTerm, resp, body, err := wp.Terms().Category().Update(newTerm.ID, term) + updatedTerm, resp, err := wp.Terms.Category().Update(ctx, newTerm.ID, term) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if updatedTerm == nil { diff --git a/terms_tag_test.go b/terms_tag_test.go index b33e249..e6b6182 100644 --- a/terms_tag_test.go +++ b/terms_tag_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "net/http" "testing" @@ -16,15 +17,12 @@ func factoryTermsTag() *wordpress.Term { func cleanUpTermsTag(t *testing.T, id int) { - wp := initTestClient() - deletedTerm, resp, body, err := wp.Terms().Tag().Delete(id, nil) + wp, ctx := initTestClient() + deletedTerm, resp, err := wp.Terms.Tag().Delete(ctx, id, nil) if err != nil { t.Errorf("Failed to clean up new term: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedTerm.ID != id { @@ -32,10 +30,10 @@ func cleanUpTermsTag(t *testing.T, id int) { } } -func getAnyOneTermsTag(t *testing.T, wp *wordpress.Client) *wordpress.Term { +func getAnyOneTermsTag(t *testing.T, ctx context.Context, wp *wordpress.Client) *wordpress.Term { - terms, resp, _, _ := wp.Terms().Tag().List(nil) - if resp.StatusCode != http.StatusOK { + terms, resp, _ := wp.Terms.Tag().List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(terms) < 1 { @@ -44,8 +42,8 @@ func getAnyOneTermsTag(t *testing.T, wp *wordpress.Client) *wordpress.Term { id := terms[0].ID - term, resp, _, _ := wp.Terms().Tag().Get(id, nil) - if resp.StatusCode != http.StatusOK { + term, resp, _ := wp.Terms.Tag().Get(ctx, id, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Fatalf("Expected 200 OK, got %v", resp.Status) } @@ -54,16 +52,13 @@ func getAnyOneTermsTag(t *testing.T, wp *wordpress.Client) *wordpress.Term { func TestTermsTagList(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() - terms, resp, body, err := wp.Terms().Tag().List(nil) + terms, resp, err := wp.Terms.Tag().List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if terms == nil { @@ -74,17 +69,14 @@ func TestTermsTagList(t *testing.T) { func TestTermsTagGet(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() - tt := getAnyOneTermsTag(t, wp) + wp, ctx := initTestClient() + tt := getAnyOneTermsTag(t, ctx, wp) - term, resp, body, err := wp.Terms().Tag().Get(tt.ID, nil) + term, resp, err := wp.Terms.Tag().Get(ctx, tt.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if term == nil { @@ -96,18 +88,15 @@ func TestTermsTagGet(t *testing.T) { func TestTermsTagCreate(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() tt := factoryTermsTag() - term, resp, body, err := wp.Terms().Tag().Create(tt) + term, resp, err := wp.Terms.Tag().Create(ctx, tt) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if term == nil { @@ -121,13 +110,13 @@ func TestTermsTagCreate(t *testing.T) { func TestTermsTagDelete(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() tt := factoryTermsTag() // create tag - newTerm, resp, _, _ := wp.Terms().Tag().Create(tt) - if resp.StatusCode != http.StatusOK { + newTerm, resp, _ := wp.Terms.Tag().Create(ctx, tt) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if newTerm == nil { @@ -135,14 +124,11 @@ func TestTermsTagDelete(t *testing.T) { } // delete tag - deletedTerm, resp, body, err := wp.Terms().Tag().Delete(newTerm.ID, nil) + deletedTerm, resp, err := wp.Terms.Tag().Delete(ctx, newTerm.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if deletedTerm == nil { @@ -153,13 +139,13 @@ func TestTermsTagDelete(t *testing.T) { func TestTermsTagUpdate(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() tt := factoryTermsTag() // create tag - newTerm, resp, _, _ := wp.Terms().Tag().Create(tt) - if resp.StatusCode != http.StatusOK { + newTerm, resp, _ := wp.Terms.Tag().Create(ctx, tt) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if newTerm == nil { @@ -167,8 +153,8 @@ func TestTermsTagUpdate(t *testing.T) { } // get tag term - term, resp, _, _ := wp.Terms().Tag().Get(newTerm.ID, nil) - if resp.StatusCode != http.StatusOK { + term, resp, _ := wp.Terms.Tag().Get(ctx, newTerm.ID, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if term == nil { @@ -183,14 +169,11 @@ func TestTermsTagUpdate(t *testing.T) { term.Description = newTermDescription // update - updatedTerm, resp, body, err := wp.Terms().Tag().Update(newTerm.ID, term) + updatedTerm, resp, err := wp.Terms.Tag().Update(ctx, newTerm.ID, term) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if updatedTerm == nil { diff --git a/terms_test.go b/terms_test.go index b3de65c..714076e 100644 --- a/terms_test.go +++ b/terms_test.go @@ -7,16 +7,13 @@ import ( func TestTermsList(t *testing.T) { t.Skipf("Not supported anymore") - wp := initTestClient() + wp, ctx := initTestClient() - terms, resp, body, err := wp.Terms().List("tag", nil) + terms, resp, err := wp.Terms.List(ctx, "tag", nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if terms == nil { diff --git a/time.go b/time.go index f60eb3e..3dcb082 100644 --- a/time.go +++ b/time.go @@ -1,21 +1,25 @@ package wordpress import ( + "fmt" "time" ) +// Location is the time.Location used when decoding timestamps from WordPress. var Location = time.UTC +// Time is a wrapper around time.Time with custom JSON marshal/unmarshal functions for the WordPress specific timestamp formats. type Time struct { time.Time } -// 2017-12-25T09:54:42 +// TimeLayout is the layout string for a timestamp without timezone information like 2017-12-25T09:54:42 const TimeLayout = "2006-01-02T15:04:05" -// "2017-09-24T13:28:06+00:00" +// TimeWithZoneLayout is the layout string for a timestamp with timezone information like 2017-09-24T13:28:06+00:00. const TimeWithZoneLayout = "2006-01-02T15:04:05-07:00" +// UnmarshalJSON unmarshals the timestamp with one of the WordPress specific formats. func (t *Time) UnmarshalJSON(b []byte) error { if b[0] == '"' && b[len(b)-1] == '"' { b = b[1 : len(b)-1] @@ -25,14 +29,14 @@ func (t *Time) UnmarshalJSON(b []byte) error { noZoneTime, altErr := time.ParseInLocation(TimeLayout, string(b), Location) if altErr != nil { return err - } else { - zoneTime = noZoneTime } + zoneTime = noZoneTime } t.Time = zoneTime return nil } +// MarshalJSON returns a WordPress formatted timestamp. func (t *Time) MarshalJSON() ([]byte, error) { - return []byte(t.Time.Format(TimeLayout)), nil + return []byte(fmt.Sprintf(`"%s"`, t.Time.Format(TimeLayout))), nil } diff --git a/types.go b/types.go index 930bdb8..5bd41a6 100644 --- a/types.go +++ b/types.go @@ -1,9 +1,11 @@ package wordpress import ( + "context" "fmt" ) +// TypeLabels represents a label that applies to a WordPress Type. type TypeLabels struct { Name string `json:"name,omitempty"` SingularName string `json:"singular_name,omitempty"` @@ -20,6 +22,8 @@ type TypeLabels struct { MenuName string `json:"menu_name,omitempty"` NameAdminBar string `json:"name_admin_bar,omitempty"` } + +// Type represents a WordPress item type. type Type struct { Description string `json:"description,omitempty"` Hierarchical bool `json:"hierarchical,omitempty"` @@ -28,26 +32,27 @@ type Type struct { Labels TypeLabels `json:"labels,omitempty"` } +// Types represents the assigned types for each item type. type Types struct { Post Type `json:"post,omitempty"` Page Type `json:"page,omitempty"` Attachment Type `json:"attachment,omitempty"` } -type TypesCollection struct { - client *Client - url string -} +// TypesService provides access to the Type related functions in the WordPress REST API. +type TypesService service -func (col *TypesCollection) List(params interface{}) (*Types, *Response, []byte, error) { +// List returns a list of types. +func (c *TypesService) List(ctx context.Context, params interface{}) (*Types, *Response, error) { var types Types - resp, body, err := col.client.List(col.url, params, &types) - return &types, newResponse(resp), body, err + resp, err := c.client.List(ctx, "types", params, &types) + return &types, resp, err } -func (col *TypesCollection) Get(slug string, params interface{}) (*Type, *Response, []byte, error) { +// Get returns a single type for the given id. +func (c *TypesService) Get(ctx context.Context, slug string, params interface{}) (*Type, *Response, error) { var entity Type - entityURL := fmt.Sprintf("%v/%v", col.url, slug) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "types", slug) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } diff --git a/types_test.go b/types_test.go index 28497ce..83dd002 100644 --- a/types_test.go +++ b/types_test.go @@ -6,36 +6,32 @@ import ( ) func TestTypesList(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - types, resp, body, err := wp.Types().List(nil) + types, resp, err := wp.Types.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if types == nil { t.Errorf("Should not return nil types") } } func TestTypesGet(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - wpType, resp, body, err := wp.Types().Get("post", nil) + wpType, resp, err := wp.Types.Get(ctx, "post", nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } - if body == nil { - t.Errorf("Should not return nil body") - } + if wpType == nil { t.Errorf("Should not return nil type") } diff --git a/users.go b/users.go index 2f70ea6..48ac24e 100644 --- a/users.go +++ b/users.go @@ -1,14 +1,18 @@ package wordpress import ( + "context" "fmt" ) +// AvatarURLS returns sizes of the users avatar. type AvatarURLS struct { Size24 string `json:"24,omitempty"` Size48 string `json:"48,omitempty"` Size96 string `json:"96,omitempty"` } + +// User represents a WordPress user. type User struct { ID int `json:"id,omitempty"` AvatarURL string `json:"avatar_url,omitempty"` @@ -31,42 +35,51 @@ type User struct { Locale string `json:"locale,omitempty"` } -type UsersCollection struct { - client *Client - url string -} +// UsersService provides access to the Users related functions in the WordPress REST API. +type UsersService service -func (col *UsersCollection) Me(params interface{}) (*User, *Response, []byte, error) { - url := fmt.Sprintf("%v/me", col.url) +// Me returns information about the currently authenticated user. +func (c *UsersService) Me(ctx context.Context, params interface{}) (*User, *Response, error) { + url := fmt.Sprintf("%v/me", "users") var user User - resp, body, err := col.client.Get(url, params, &user) - return &user, newResponse(resp), body, err + resp, err := c.client.Get(ctx, url, params, &user) + return &user, resp, err } -func (col *UsersCollection) List(params interface{}) ([]User, *Response, []byte, error) { - var users []User - resp, body, err := col.client.List(col.url, params, &users) - return users, newResponse(resp), body, err + +// List returns a list of users. +func (c *UsersService) List(ctx context.Context, params interface{}) ([]*User, *Response, error) { + var users []*User + resp, err := c.client.List(ctx, "users", params, &users) + return users, resp, err } -func (col *UsersCollection) Create(new *User) (*User, *Response, []byte, error) { + +// Create creates a new user. +func (c *UsersService) Create(ctx context.Context, new *User) (*User, *Response, error) { var created User - resp, body, err := col.client.Create(col.url, new, &created) - return &created, newResponse(resp), body, err + resp, err := c.client.Create(ctx, "users", new, &created) + return &created, resp, err } -func (col *UsersCollection) Get(id int, params interface{}) (*User, *Response, []byte, error) { + +// Get returns a single term for the given id. +func (c *UsersService) Get(ctx context.Context, id int, params interface{}) (*User, *Response, error) { var entity User - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Get(entityURL, params, &entity) - return &entity, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "users", id) + resp, err := c.client.Get(ctx, entityURL, params, &entity) + return &entity, resp, err } -func (col *UsersCollection) Update(id int, post *User) (*User, *Response, []byte, error) { + +// Update updates a single term with the given id. +func (c *UsersService) Update(ctx context.Context, id int, post *User) (*User, *Response, error) { var updated User - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Update(entityURL, post, &updated) - return &updated, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "users", id) + resp, err := c.client.Update(ctx, entityURL, post, &updated) + return &updated, resp, err } -func (col *UsersCollection) Delete(id int, params interface{}) (*User, *Response, []byte, error) { + +// Delete removes the term with the given id. +func (c *UsersService) Delete(ctx context.Context, id int, params interface{}) (*User, *Response, error) { var deleted User - entityURL := fmt.Sprintf("%v/%v", col.url, id) - resp, body, err := col.client.Delete(entityURL, params, &deleted) - return &deleted, newResponse(resp), body, err + entityURL := fmt.Sprintf("%v/%v", "users", id) + resp, err := c.client.Delete(ctx, entityURL, params, &deleted) + return &deleted, resp, err } diff --git a/users_test.go b/users_test.go index 88a9b22..eb2b311 100644 --- a/users_test.go +++ b/users_test.go @@ -1,6 +1,7 @@ package wordpress_test import ( + "context" "net/http" "testing" @@ -16,28 +17,27 @@ func factoryUser() *wordpress.User { Password: "password", } } + func cleanUpUser(t *testing.T, userID int) { - wp := initTestClient() + wp, ctx := initTestClient() // Note that deleting a user requires `force=true` since `users` resource does not support trashing - deletedUser, resp, body, err := wp.Users().Delete(userID, "force=true") + deletedUser, resp, err := wp.Users.Delete(ctx, userID, "force=true&reassign=1") if err != nil { t.Errorf("Failed to clean up new user: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if deletedUser.ID != userID { t.Errorf("Deleted user ID should be the same as newly created user: %v != %v", deletedUser.ID, userID) } } -func getAnyOneUser(t *testing.T, wp *wordpress.Client) *wordpress.User { - users, resp, _, _ := wp.Users().List(nil) - if resp.StatusCode != http.StatusOK { +func getAnyOneUser(t *testing.T, ctx context.Context, wp *wordpress.Client) *wordpress.User { + + users, resp, _ := wp.Users.List(ctx, nil) + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if len(users) < 1 { @@ -46,24 +46,21 @@ func getAnyOneUser(t *testing.T, wp *wordpress.Client) *wordpress.User { userID := users[0].ID - user, resp, _, _ := wp.Users().Get(userID, "context=edit") - if resp.StatusCode != http.StatusOK { + user, resp, _ := wp.Users.Get(ctx, userID, "context=edit") + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } return user } func TestUsersList(t *testing.T) { - client := initTestClient() + client, ctx := initTestClient() - users, resp, body, err := client.Users().List(nil) + users, resp, err := client.Users.List(ctx, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if users == nil { @@ -72,16 +69,13 @@ func TestUsersList(t *testing.T) { } func TestUsersMe(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - currentUser, resp, body, err := wp.Users().Me("context=edit") + currentUser, resp, err := wp.Users.Me(ctx, "context=edit") if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if currentUser == nil { @@ -93,18 +87,15 @@ func TestUsersMe(t *testing.T) { } func TestUsersGet(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() - u := getAnyOneUser(t, wp) + u := getAnyOneUser(t, ctx, wp) - user, resp, body, err := wp.Users().Get(u.ID, nil) + user, resp, err := wp.Users.Get(ctx, u.ID, nil) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 StatusOK, got %v", resp.Status) } if user == nil { @@ -113,7 +104,7 @@ func TestUsersGet(t *testing.T) { } func TestUsersCreate(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() u := &wordpress.User{ Username: "go-wordpress-test-user1", @@ -123,14 +114,11 @@ func TestUsersCreate(t *testing.T) { Password: "password", } - newUser, resp, body, err := wp.Users().Create(u) + newUser, resp, err := wp.Users.Create(ctx, u) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusCreated { + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } if newUser == nil { @@ -142,11 +130,11 @@ func TestUsersCreate(t *testing.T) { } func TestUsersDelete(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() u := factoryUser() - newUser, resp, _, _ := wp.Users().Create(u) - if resp.StatusCode != http.StatusCreated { + newUser, resp, _ := wp.Users.Create(ctx, u) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } if newUser == nil { @@ -155,14 +143,11 @@ func TestUsersDelete(t *testing.T) { // Note that deleting a user requires `force=true` since `users` resource does not support trashing // If not specified, a 501 NotImplemented will be returned - deletedUser, resp, body, err := wp.Users().Delete(newUser.ID, "force=true") + deletedUser, resp, err := wp.Users.Delete(ctx, newUser.ID, "force=true&reassign=1") if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if deletedUser.ID != newUser.ID { @@ -171,12 +156,12 @@ func TestUsersDelete(t *testing.T) { } func TestUsersUpdate(t *testing.T) { - wp := initTestClient() + wp, ctx := initTestClient() u := factoryUser() // create user - newUser, resp, _, _ := wp.Users().Create(u) - if resp.StatusCode != http.StatusCreated { + newUser, resp, _ := wp.Users.Create(ctx, u) + if resp != nil && resp.StatusCode != http.StatusCreated { t.Errorf("Expected 201 Created, got %v", resp.Status) } if newUser == nil { @@ -184,8 +169,8 @@ func TestUsersUpdate(t *testing.T) { } // get user in `edit` context - user, resp, _, _ := wp.Users().Get(newUser.ID, "context=edit") - if resp.StatusCode != http.StatusOK { + user, resp, _ := wp.Users.Get(ctx, newUser.ID, "context=edit") + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if user == nil { @@ -200,14 +185,11 @@ func TestUsersUpdate(t *testing.T) { user.Email = newUserEmail // update - updatedUser, resp, body, err := wp.Users().Update(user.ID, user) + updatedUser, resp, err := wp.Users.Update(ctx, user.ID, user) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } - if body == nil { - t.Errorf("body should not be nil") - } - if resp.StatusCode != http.StatusOK { + if resp != nil && resp.StatusCode != http.StatusOK { t.Errorf("Expected 200 OK, got %v", resp.Status) } if updatedUser.ID != user.ID { diff --git a/utils.go b/utils.go index 9db7db9..eebcb8f 100644 --- a/utils.go +++ b/utils.go @@ -1,63 +1,10 @@ package wordpress import ( - "bytes" - "encoding/json" - "errors" "fmt" "log" - "net/http" - "os" - - "github.com/parnurzeal/gorequest" ) -var DEBUG bool = (os.Getenv("DEBUG") == "1") - -func unmarshalResponse(resp gorequest.Response, body []byte, result interface{}) error { - - var prettyJSON bytes.Buffer - err2 := json.Indent(&prettyJSON, body, "", " ") - if err2 != nil { - log.Println("JSON parse error: ", err2) - - if DEBUG { - log.Println("body: ", string(body)) - } - } - if DEBUG { - log.Println("body: ", string(prettyJSON.Bytes())) - } - - if resp.StatusCode != http.StatusOK && - resp.StatusCode != http.StatusCreated && - resp.StatusCode != http.StatusAccepted { - return errors.New(resp.Status) - } - - err := json.Unmarshal(body, result) - if err != nil { - log.Println("JSON parse error: ", err) - return err - } - return nil -} - func _warning(v ...interface{}) { log.Println(fmt.Sprintln("[go-wordpress]", v)) } - -func _log(v ...interface{}) { - log.Println(fmt.Sprintln("[go-wordpress]", v)) -} - -// UnmarshalServerError A helper function to unmarshal error response from server -func UnmarshalServerError(body []byte) ([]GeneralError, error) { - var resp []GeneralError - err := json.Unmarshal(body, &resp) - if err != nil { - log.Println("JSON parse error: ", err) - return nil, err - } - return resp, nil -} From 5e973a885e876d54cd2029d0a038684d19ae38fe Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 4 Feb 2018 00:29:08 -0800 Subject: [PATCH 22/46] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 9cc0e46..5a2e864 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,4 @@ go test ``` ## TODO -- [ ] `godoc` documentation, so its easier for library users to map the REST APIs to library calls -- [ ] Test `comments` API endpoint. (Currently, already implemented but not tested due to WP-API issues with creating comments reliably) -- [ ] Support OAuth authentication +- [ ] Support OAuth authentication (already supported by passing in your own HTTP client) From 782b5bdb432e87d5e3eba5443636dfdabc7cea71 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 4 Feb 2018 16:19:40 -0800 Subject: [PATCH 23/46] Stop using new as a variable as it is a reserved keyword --- categories.go | 4 ++-- comments.go | 4 ++-- pages.go | 4 ++-- tags.go | 4 ++-- terms.go | 4 ++-- users.go | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/categories.go b/categories.go index 6dced16..986a551 100644 --- a/categories.go +++ b/categories.go @@ -53,9 +53,9 @@ func (c *CategoriesService) List(ctx context.Context, opts *CategoriesListOption } // Create creates a new category. -func (c *CategoriesService) Create(ctx context.Context, new *Category) (*Category, *Response, error) { +func (c *CategoriesService) Create(ctx context.Context, newCategory *Category) (*Category, *Response, error) { var created Category - resp, err := c.client.Create(ctx, "categories", new, &created) + resp, err := c.client.Create(ctx, "categories", newCategory, &created) return &created, resp, err } diff --git a/comments.go b/comments.go index b0eace2..41dcb08 100644 --- a/comments.go +++ b/comments.go @@ -71,9 +71,9 @@ func (c *CommentsService) List(ctx context.Context, opts *CommentsListOptions) ( } // Create creates a new comment. -func (c *CommentsService) Create(ctx context.Context, new *Comment) (*Comment, *Response, error) { +func (c *CommentsService) Create(ctx context.Context, newComment *Comment) (*Comment, *Response, error) { var created Comment - resp, err := c.client.Create(ctx, "comments", new, &created) + resp, err := c.client.Create(ctx, "comments", newComment, &created) return &created, resp, err } diff --git a/pages.go b/pages.go index 8b00463..a36bc3b 100644 --- a/pages.go +++ b/pages.go @@ -107,9 +107,9 @@ func (c *PagesService) List(ctx context.Context, opts *PagesListOptions) ([]*Pag } // Create creates a new page. -func (c *PagesService) Create(ctx context.Context, new *Page) (*Page, *Response, error) { +func (c *PagesService) Create(ctx context.Context, newPage *Page) (*Page, *Response, error) { var created Page - resp, err := c.client.Create(ctx, "pages", new, &created) + resp, err := c.client.Create(ctx, "pages", newPage, &created) created.setService(c) diff --git a/tags.go b/tags.go index d7d2111..755bf16 100644 --- a/tags.go +++ b/tags.go @@ -53,9 +53,9 @@ func (c *TagsService) List(ctx context.Context, opts *TagsListOptions) ([]*Tag, } // Create creates a new tag. -func (c *TagsService) Create(ctx context.Context, new *Tag) (*Tag, *Response, error) { +func (c *TagsService) Create(ctx context.Context, newTag *Tag) (*Tag, *Response, error) { var created Tag - resp, err := c.client.Create(ctx, "tags", new, &created) + resp, err := c.client.Create(ctx, "tags", newTag, &created) return &created, resp, err } diff --git a/terms.go b/terms.go index 1264239..62dba06 100644 --- a/terms.go +++ b/terms.go @@ -61,9 +61,9 @@ func (c *TermsTaxonomyService) List(ctx context.Context, params interface{}) ([] } // Create creates a new term. -func (c *TermsTaxonomyService) Create(ctx context.Context, new *Term) (*Term, *Response, error) { +func (c *TermsTaxonomyService) Create(ctx context.Context, newTerm *Term) (*Term, *Response, error) { var created Term - resp, err := c.client.Create(ctx, c.url, new, &created) + resp, err := c.client.Create(ctx, c.url, newTerm, &created) return &created, resp, err } diff --git a/users.go b/users.go index 48ac24e..81feb3c 100644 --- a/users.go +++ b/users.go @@ -54,9 +54,9 @@ func (c *UsersService) List(ctx context.Context, params interface{}) ([]*User, * } // Create creates a new user. -func (c *UsersService) Create(ctx context.Context, new *User) (*User, *Response, error) { +func (c *UsersService) Create(ctx context.Context, newUser *User) (*User, *Response, error) { var created User - resp, err := c.client.Create(ctx, "users", new, &created) + resp, err := c.client.Create(ctx, "users", newUser, &created) return &created, resp, err } From 1e265be6043dbb5ddeed287953e549d2c9d223ba Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 4 Feb 2018 20:30:49 -0800 Subject: [PATCH 24/46] cleanup entityURLs --- categories.go | 6 +++--- comments.go | 6 +++--- media.go | 4 ++-- pages.go | 6 +++--- posts.go | 6 +++--- statuses.go | 2 +- tags.go | 6 +++--- taxonomies.go | 2 +- terms.go | 2 +- types.go | 2 +- users.go | 6 +++--- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/categories.go b/categories.go index 986a551..4550b5a 100644 --- a/categories.go +++ b/categories.go @@ -62,7 +62,7 @@ func (c *CategoriesService) Create(ctx context.Context, newCategory *Category) ( // Get returns a single category for the given id. func (c *CategoriesService) Get(ctx context.Context, id int, params interface{}) (*Category, *Response, error) { var entity Category - entityURL := fmt.Sprintf("%v/%v", "categories", id) + entityURL := fmt.Sprintf("categories/%v", id) resp, err := c.client.Get(ctx, entityURL, params, &entity) return &entity, resp, err } @@ -70,7 +70,7 @@ func (c *CategoriesService) Get(ctx context.Context, id int, params interface{}) // Update updates a single category with the given id. func (c *CategoriesService) Update(ctx context.Context, id int, post *Category) (*Category, *Response, error) { var updated Category - entityURL := fmt.Sprintf("%v/%v", "categories", id) + entityURL := fmt.Sprintf("categories/%v", id) resp, err := c.client.Update(ctx, entityURL, post, &updated) return &updated, resp, err } @@ -78,7 +78,7 @@ func (c *CategoriesService) Update(ctx context.Context, id int, post *Category) // Delete removes the category with the given id. func (c *CategoriesService) Delete(ctx context.Context, id int, params interface{}) (*Category, *Response, error) { var deleted Category - entityURL := fmt.Sprintf("%v/%v", "categories", id) + entityURL := fmt.Sprintf("categories/%v", id) resp, err := c.client.Delete(ctx, entityURL, params, &deleted) return &deleted, resp, err } diff --git a/comments.go b/comments.go index 41dcb08..14ce8bc 100644 --- a/comments.go +++ b/comments.go @@ -80,7 +80,7 @@ func (c *CommentsService) Create(ctx context.Context, newComment *Comment) (*Com // Get returns a single comment for the given id. func (c *CommentsService) Get(ctx context.Context, id int, params interface{}) (*Comment, *Response, error) { var entity Comment - entityURL := fmt.Sprintf("%v/%v", "comments", id) + entityURL := fmt.Sprintf("comments/%v", id) resp, err := c.client.Get(ctx, entityURL, params, &entity) return &entity, resp, err } @@ -88,7 +88,7 @@ func (c *CommentsService) Get(ctx context.Context, id int, params interface{}) ( // Update updates a single comment with the given id. func (c *CommentsService) Update(ctx context.Context, id int, post *Comment) (*Comment, *Response, error) { var updated Comment - entityURL := fmt.Sprintf("%v/%v", "comments", id) + entityURL := fmt.Sprintf("comments/%v", id) resp, err := c.client.Update(ctx, entityURL, post, &updated) return &updated, resp, err } @@ -96,7 +96,7 @@ func (c *CommentsService) Update(ctx context.Context, id int, post *Comment) (*C // Delete removes the comment with the given id. func (c *CommentsService) Delete(ctx context.Context, id int, params interface{}) (*Comment, *Response, error) { var deleted Comment - entityURL := fmt.Sprintf("%v/%v", "comments", id) + entityURL := fmt.Sprintf("comments/%v", id) resp, err := c.client.Delete(ctx, entityURL, params, &deleted) return &deleted, resp, err } diff --git a/media.go b/media.go index 6a148ff..bec5ae7 100644 --- a/media.go +++ b/media.go @@ -120,7 +120,7 @@ func (c *MediaService) Create(ctx context.Context, options *MediaUploadOptions) // Get returns a single media item for the given id. func (c *MediaService) Get(ctx context.Context, id int, params interface{}) (*Media, *Response, error) { var entity Media - entityURL := fmt.Sprintf("%v/%v", "media", id) + entityURL := fmt.Sprintf("media/%v", id) resp, err := c.client.Get(ctx, entityURL, params, &entity) return &entity, resp, err } @@ -128,7 +128,7 @@ func (c *MediaService) Get(ctx context.Context, id int, params interface{}) (*Me // Delete removes the media item with the given id. func (c *MediaService) Delete(ctx context.Context, id int, params interface{}) (*Media, *Response, error) { var deleted Media - entityURL := fmt.Sprintf("%v/%v", "media", id) + entityURL := fmt.Sprintf("media/%v", id) resp, err := c.client.Delete(ctx, entityURL, params, &deleted) return &deleted, resp, err } diff --git a/pages.go b/pages.go index a36bc3b..7d47606 100644 --- a/pages.go +++ b/pages.go @@ -119,7 +119,7 @@ func (c *PagesService) Create(ctx context.Context, newPage *Page) (*Page, *Respo // Get returns a single page for the given id. func (c *PagesService) Get(ctx context.Context, id int, params interface{}) (*Page, *Response, error) { var entity Page - entityURL := fmt.Sprintf("%v/%v", "pages", id) + entityURL := fmt.Sprintf("pages/%v", id) resp, err := c.client.Get(ctx, entityURL, params, &entity) // set collection object for each entity which has sub-collection @@ -140,7 +140,7 @@ func (c *PagesService) Entity(id int) *Page { // Update updates a single page with the given id. func (c *PagesService) Update(ctx context.Context, id int, page *Page) (*Page, *Response, error) { var updated Page - entityURL := fmt.Sprintf("%v/%v", "pages", id) + entityURL := fmt.Sprintf("pages/%v", id) resp, err := c.client.Update(ctx, entityURL, page, &updated) // set collection object for each entity which has sub-collection @@ -152,7 +152,7 @@ func (c *PagesService) Update(ctx context.Context, id int, page *Page) (*Page, * // Delete removes the page with the given id. func (c *PagesService) Delete(ctx context.Context, id int, params interface{}) (*Page, *Response, error) { var deleted Page - entityURL := fmt.Sprintf("%v/%v", "pages", id) + entityURL := fmt.Sprintf("pages/%v", id) resp, err := c.client.Delete(ctx, entityURL, params, &deleted) diff --git a/posts.go b/posts.go index d499238..0ee5d3e 100644 --- a/posts.go +++ b/posts.go @@ -174,7 +174,7 @@ func (c *PostsService) Create(ctx context.Context, newPost *Post) (*Post, *Respo // Get returns a single post for the given id. func (c *PostsService) Get(ctx context.Context, id int, params interface{}) (*Post, *Response, error) { var entity Post - entityURL := fmt.Sprintf("%v/%v", "posts", id) + entityURL := fmt.Sprintf("posts/%v", id) resp, err := c.client.Get(ctx, entityURL, params, &entity) // set collection object for each entity which has sub-collection @@ -195,7 +195,7 @@ func (c *PostsService) Entity(id int) *Post { // Update updates a single post with the given id. func (c *PostsService) Update(ctx context.Context, id int, post *Post) (*Post, *Response, error) { var updated Post - entityURL := fmt.Sprintf("%v/%v", "posts", id) + entityURL := fmt.Sprintf("posts/%v", id) resp, err := c.client.Update(ctx, entityURL, post, &updated) // set collection object for each entity which has sub-collection @@ -207,7 +207,7 @@ func (c *PostsService) Update(ctx context.Context, id int, post *Post) (*Post, * // Delete removes the post with the given id. func (c *PostsService) Delete(ctx context.Context, id int, params interface{}) (*Post, *Response, error) { var deleted Post - entityURL := fmt.Sprintf("%v/%v", "posts", id) + entityURL := fmt.Sprintf("posts/%v", id) resp, err := c.client.Delete(ctx, entityURL, params, &deleted) diff --git a/statuses.go b/statuses.go index 7655e62..1a28b5a 100644 --- a/statuses.go +++ b/statuses.go @@ -37,7 +37,7 @@ func (c *StatusesService) List(ctx context.Context, params interface{}) (*Status // Get returns a single status for the given id. func (c *StatusesService) Get(ctx context.Context, slug string, params interface{}) (*Status, *Response, error) { var entity Status - entityURL := fmt.Sprintf("%v/%v", "statuses", slug) + entityURL := fmt.Sprintf("statuses/%v", slug) resp, err := c.client.Get(ctx, entityURL, params, &entity) return &entity, resp, err } diff --git a/tags.go b/tags.go index 755bf16..ce7e971 100644 --- a/tags.go +++ b/tags.go @@ -62,7 +62,7 @@ func (c *TagsService) Create(ctx context.Context, newTag *Tag) (*Tag, *Response, // Get returns a single tag for the given id. func (c *TagsService) Get(ctx context.Context, id int, params interface{}) (*Tag, *Response, error) { var entity Tag - entityURL := fmt.Sprintf("%v/%v", "tags", id) + entityURL := fmt.Sprintf("tags/%v", id) resp, err := c.client.Get(ctx, entityURL, params, &entity) return &entity, resp, err } @@ -70,7 +70,7 @@ func (c *TagsService) Get(ctx context.Context, id int, params interface{}) (*Tag // Update updates a single tag with the given id. func (c *TagsService) Update(ctx context.Context, id int, post *Tag) (*Tag, *Response, error) { var updated Tag - entityURL := fmt.Sprintf("%v/%v", "tags", id) + entityURL := fmt.Sprintf("tags/%v", id) resp, err := c.client.Update(ctx, entityURL, post, &updated) return &updated, resp, err } @@ -78,7 +78,7 @@ func (c *TagsService) Update(ctx context.Context, id int, post *Tag) (*Tag, *Res // Delete removes the tag with the given id. func (c *TagsService) Delete(ctx context.Context, id int, params interface{}) (*Tag, *Response, error) { var deleted Tag - entityURL := fmt.Sprintf("%v/%v", "tags", id) + entityURL := fmt.Sprintf("tags/%v", id) resp, err := c.client.Delete(ctx, entityURL, params, &deleted) return &deleted, resp, err } diff --git a/taxonomies.go b/taxonomies.go index 3c0c362..c6ec147 100644 --- a/taxonomies.go +++ b/taxonomies.go @@ -29,7 +29,7 @@ func (c *TaxonomiesService) List(ctx context.Context, params interface{}) (map[s // Get returns a single taxonomy for the given id. func (c *TaxonomiesService) Get(ctx context.Context, slug string, params interface{}) (*Taxonomy, *Response, error) { var taxonomy Taxonomy - entityURL := fmt.Sprintf("%v/%v", "taxonomies", slug) + entityURL := fmt.Sprintf("taxonomies/%v", slug) resp, err := c.client.Get(ctx, entityURL, params, &taxonomy) return &taxonomy, resp, err } diff --git a/terms.go b/terms.go index 62dba06..facabc1 100644 --- a/terms.go +++ b/terms.go @@ -23,7 +23,7 @@ type TermsService service // List returns a list of terms. func (c *TermsService) List(ctx context.Context, taxonomy string, params interface{}) ([]*Term, *Response, error) { var terms []*Term - url := fmt.Sprintf("%v/%v", "terms", taxonomy) + url := fmt.Sprintf("terms/%v", taxonomy) resp, err := c.client.List(ctx, url, params, &terms) return terms, resp, err } diff --git a/types.go b/types.go index 5bd41a6..597fd0f 100644 --- a/types.go +++ b/types.go @@ -52,7 +52,7 @@ func (c *TypesService) List(ctx context.Context, params interface{}) (*Types, *R // Get returns a single type for the given id. func (c *TypesService) Get(ctx context.Context, slug string, params interface{}) (*Type, *Response, error) { var entity Type - entityURL := fmt.Sprintf("%v/%v", "types", slug) + entityURL := fmt.Sprintf("types/%v", slug) resp, err := c.client.Get(ctx, entityURL, params, &entity) return &entity, resp, err } diff --git a/users.go b/users.go index 81feb3c..a4d897a 100644 --- a/users.go +++ b/users.go @@ -63,7 +63,7 @@ func (c *UsersService) Create(ctx context.Context, newUser *User) (*User, *Respo // Get returns a single term for the given id. func (c *UsersService) Get(ctx context.Context, id int, params interface{}) (*User, *Response, error) { var entity User - entityURL := fmt.Sprintf("%v/%v", "users", id) + entityURL := fmt.Sprintf("users/%v", id) resp, err := c.client.Get(ctx, entityURL, params, &entity) return &entity, resp, err } @@ -71,7 +71,7 @@ func (c *UsersService) Get(ctx context.Context, id int, params interface{}) (*Us // Update updates a single term with the given id. func (c *UsersService) Update(ctx context.Context, id int, post *User) (*User, *Response, error) { var updated User - entityURL := fmt.Sprintf("%v/%v", "users", id) + entityURL := fmt.Sprintf("users/%v", id) resp, err := c.client.Update(ctx, entityURL, post, &updated) return &updated, resp, err } @@ -79,7 +79,7 @@ func (c *UsersService) Update(ctx context.Context, id int, post *User) (*User, * // Delete removes the term with the given id. func (c *UsersService) Delete(ctx context.Context, id int, params interface{}) (*User, *Response, error) { var deleted User - entityURL := fmt.Sprintf("%v/%v", "users", id) + entityURL := fmt.Sprintf("users/%v", id) resp, err := c.client.Delete(ctx, entityURL, params, &deleted) return &deleted, resp, err } From c80cf64b27754f4302200fbc92aa14385cd3fc77 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 4 Feb 2018 22:24:01 -0800 Subject: [PATCH 25/46] Remove authentication information from the client, cleanup the README.md, provide example implementations of basic auth, OAuth 1.0a and OAuth 2.0/JWT. Fix a little bug in decoding to Error. --- README.md | 127 +++++++++++++++++++++++++++----------- client.go | 33 +--------- client_test.go | 26 ++++---- example/basicauth/main.go | 30 +++++++++ example/oauth1/main.go | 79 ++++++++++++++++++++++++ example/oauth2/main.go | 28 +++++++++ transports.go | 46 ++++++++++++++ 7 files changed, 287 insertions(+), 82 deletions(-) create mode 100644 example/basicauth/main.go create mode 100644 example/oauth1/main.go create mode 100644 example/oauth2/main.go create mode 100644 transports.go diff --git a/README.md b/README.md index 5a2e864..46c2380 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ A Go client library for the [Wordpress REST API](https://developer.wordpress.org ```bash go get github.com/robbiet480/go-wordpress - ``` ## Usage @@ -18,23 +17,29 @@ go get github.com/robbiet480/go-wordpress package main import ( - "github.com/robbiet480/go-wordpress" - "net/http" + "context" + "log" + "net/http" + + "github.com/robbiet480/go-wordpress" ) func main() { + tp := wordpress.BasicAuthTransport{ + Username: USER, + Password: PASSWORD, + } + // create wp-api client client := wordpress.NewClient(&wordpress.Options{ BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json/` - Username: USER, - Password: PASSWORD, - }, nil) + }, tp.Client()) ctx := context.Background() // for eg, to get current user (GET /users/me) - currentUser, resp, _ := client.Users.Me(ctx) + currentUser, resp, _ := client.Users.Me(ctx, nil) if resp != nil && resp.StatusCode != http.StatusOK { // handle error } @@ -45,15 +50,51 @@ func main() { resp, err := client.Get(ctx, "/posts/100", nil, &obj) // ... - log.Println("Current user", currentUser) + fmt.Printf("Current user %+v", currentUser) } - ``` + For more examples, see package tests. For list of supported/implemented endpoints, see [Endpoints.md](./endpoints.md) -### Pagination ### +### Authentication + +The go-wordpress library does not directly handle authentication. Instead, when +creating a new client, pass an `http.Client` that can handle authentication for +you. + +Note that when using an authenticated Client, all calls made by the client will +include the specified authentication transport token. Therefore, authenticated clients should +almost never be shared between different users. + +#### Username/Password or Application Password + +A basic authentication (username/password) client for use with +the [WP-API BasicAuth plugin](https://github.com/WP-API/Basic-Auth) +or [Application Passwords plugin](https://wordpress.org/plugins/application-passwords/) +is included with the library. +An example implementation can be found in [example/basicauth/main.go](example/basicauth/main.go). + +#### OAuth 1.0a + +If you use the [OAuth 1.0a Server](https://github.com/WP-API/OAuth1) for authentication, +you can find an example implementation in [example/oauth2/main.go](example/oauth2/main.go) using the oauth1 library +(which is very similar to the official OAuth 2.0 library). +See the [oauth1 docs](https://godoc.org/dghubble/oauth1) for complete instructions on using that library. + +#### OAuth 2.0 and JWT + +If you are using the [JWT](https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/) plug-in for authentication, +you can use the [oauth2](https://github.com/golang/oauth2) library's `StaticTokenSource`. +An example implementation can be found in [example/oauth2/main.go](example/oauth2/main.go). +See the [oauth2 docs](https://godoc.org/golang.org/x/oauth2) for complete instructions on using that library. + +#### Other authentication styles + +For any other authentication methods, you should only need to provide a custom `http.Client` when creating a new WordPress client. + +### Pagination All requests for resource collections (posts, pages, media, revisions, etc.) support pagination. Pagination options are described in the @@ -63,37 +104,51 @@ embedded type of a more specific list options struct (for example `wordpress.Response` struct. ```go -client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json/` - Username: USER, - Password: PASSWORD, -}, nil) +package main -ctx := context.Background() +import ( + "context" -opt := &wordpress.PostsByOrgOptions{ - ListOptions: wordpress.ListOptions{PerPage: 10}, -} -// get all pages of results -var allPosts []*wordpress.Post -for { - posts, resp, err := client.Posts.List(ctx, opt) - if err != nil { - return err + "github.com/robbiet480/go-wordpress" +) + +func main() { + tp := wordpress.BasicAuthTransport{ + Username: USER, + Password: PASSWORD, + } + + // create wp-api client + client := wordpress.NewClient(&wordpress.Options{ + BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json/` + }, tp.Client()) + + ctx := context.Background() + + opt := &wordpress.PostsByOrgOptions{ + ListOptions: wordpress.ListOptions{PerPage: 10}, } - allPosts = append(allPosts, posts...) - if resp.NextPage == 0 { - break + // get all pages of results + var allPosts []*wordpress.Post + for { + posts, resp, err := client.Posts.List(ctx, opt) + if err != nil { + return err + } + allPosts = append(allPosts, posts...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage } - opt.Page = resp.NextPage } ``` ## Test + __Note:__ Before running the tests, ensure that you have set up your test environment - ### Prerequisites - Wordpress 4.x - [WP-API's BasicAuth plugin (for authentication)](https://github.com/WP-API/Basic-Auth) @@ -105,20 +160,18 @@ Before running the tests, ensure that you have set up your test environment - Edit one (1) most recent Post to create a revision - Edit one (1) most recent Page to create a revision -## Running test - +## Running tests ```bash - # Set test enviroment export WP_API_URL=http://192.168.99.100:32777/wp-json/ export WP_USER= export WP_PASSWD= -cd /github.com/robbiet480/go-wordpress +cd $GOPATH/src/github.com/robbiet480/go-wordpress go test - ``` -## TODO -- [ ] Support OAuth authentication (already supported by passing in your own HTTP client) +## Thanks + +Large parts of this library were inspired if not outright copied from Google's excellent [`go-github`](https://github.com/google/go-github) library. diff --git a/client.go b/client.go index 6328405..cfd508c 100644 --- a/client.go +++ b/client.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "mime/multipart" "net/http" "net/url" @@ -30,7 +29,7 @@ type Error struct { Response *http.Response // HTTP response that caused this error Code string `json:"code"` Message string `json:"message"` - Data int `json:"data"` // Unsure if this is consistent + Data interface{} `json:"data"` } func (e *Error) Error() string { @@ -44,13 +43,6 @@ type Options struct { BaseAPIURL string Location *time.Location - // Basic Auth - Username string - Password string - - JWTToken string - // TODO: support OAuth authentication - // User agent used when communicating with the WordPress API. UserAgent string } @@ -169,14 +161,6 @@ func NewClient(options *Options, httpClient *http.Client) *Client { DisableKeepAlives: true, }, } - - httpClient.CheckRedirect = func(r *http.Request, via []*http.Request) error { - // perform BasicAuth on each redirect request. - // (requests are cookie-less; so we need to keep re-auth-ing again) - r.SetBasicAuth(options.Username, options.Password) - log.Println("REDIRECT", r, options.Username, options.Password) - return nil - } } c := &Client{client: httpClient, options: options, BaseURL: url} @@ -254,12 +238,6 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ return nil, err } - if c.options.Username != "" && c.options.Password != "" { - req.SetBasicAuth(c.options.Username, c.options.Password) - } else if c.options.JWTToken != "" { - req.Header.Add("Authorization", c.options.JWTToken) - } - if body != nil { req.Header.Set("Content-Type", "application/json") } @@ -486,12 +464,6 @@ func (c *Client) PostData(ctx context.Context, urlStr string, content []byte, co return nil, err } - if c.options.Username != "" && c.options.Password != "" { - req.SetBasicAuth(c.options.Username, c.options.Password) - } else if c.options.JWTToken != "" { - req.Header.Add("Authorization", c.options.JWTToken) - } - if c.options.UserAgent != "" { req.Header.Set("User-Agent", c.options.UserAgent) } @@ -499,9 +471,6 @@ func (c *Client) PostData(ctx context.Context, urlStr string, content []byte, co req.Header.Set("Content-Type", w.FormDataContentType()) req.Header.Set("Content-Disposition", fmt.Sprintf("filename=%v", filename)) - // Set Transport - // s.Client.Transport = s.Transport - // Send request return c.Do(ctx, req, &result) } diff --git a/client_test.go b/client_test.go index 4a426c1..bd7da74 100644 --- a/client_test.go +++ b/client_test.go @@ -15,11 +15,17 @@ var PASSWORD string = os.Getenv("WP_PASSWD") var API_BASE_URL string = os.Getenv("WP_API_URL") func TestClientNew(t *testing.T) { + tp := wordpress.BasicAuthTransport{ + Username: USER, + Password: PASSWORD, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // needs to be disabled for Lets Encrypt for whatever reason + DisableKeepAlives: true, + }, + } client := wordpress.NewClient(&wordpress.Options{ BaseAPIURL: API_BASE_URL, - Username: USER, - Password: PASSWORD, - }, nil) + }, tp.Client()) if client == nil { t.Fatalf("Client should not be nil") } @@ -36,22 +42,16 @@ func initTestClient() (*wordpress.Client, context.Context) { panic("Please set your environment before running the tests") } - httpClient := &http.Client{ - Jar: nil, + tp := wordpress.BasicAuthTransport{ + Username: USER, + Password: PASSWORD, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // needs to be disabled for Lets Encrypt for whatever reason DisableKeepAlives: true, }, } - httpClient.CheckRedirect = func(r *http.Request, via []*http.Request) error { - r.SetBasicAuth(USER, PASSWORD) - return nil - } - return wordpress.NewClient(&wordpress.Options{ BaseAPIURL: API_BASE_URL, - Username: USER, - Password: PASSWORD, - }, httpClient), context.Background() + }, tp.Client()), context.Background() } diff --git a/example/basicauth/main.go b/example/basicauth/main.go new file mode 100644 index 0000000..f74558d --- /dev/null +++ b/example/basicauth/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "log" + + "github.com/robbiet480/go-wordpress" +) + +func main() { + + tp := wordpress.BasicAuthTransport{ + Username: "username", + Password: "password", + } + + // create wp-api client + client := wordpress.NewClient(&wordpress.Options{ + BaseAPIURL: "http://192.168.99.100:32777/wp-json/", + }, tp.Client()) + + ctx := context.Background() + + // get the currently authenticated users details + authenticatedUser, _, err := client.Users.Me(ctx, nil) + if err != nil { + log.Fatalln(err) + } + log.Printf("Authenticated user %+v", authenticatedUser) +} diff --git a/example/oauth1/main.go b/example/oauth1/main.go new file mode 100644 index 0000000..635e454 --- /dev/null +++ b/example/oauth1/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/dghubble/oauth1" + "github.com/robbiet480/go-wordpress" +) + +var config oauth1.Config + +// main performs the WordPress OAuth1 user flow from the command line +func main() { + config = oauth1.Config{ + ConsumerKey: "CONSUMER_KEY", + ConsumerSecret: "CONSUMER_SECRET", + CallbackURL: "http://localhost:8080/callback", + Endpoint: oauth1.Endpoint{ + RequestTokenURL: "http://192.168.99.100:32777/oauth1/request", + AuthorizeURL: "http://192.168.99.100:32777/oauth1/authorize", + AccessTokenURL: "http://192.168.99.100:32777/oauth1/access", + }, + } + + requestToken, requestSecret, err := login() + if err != nil { + log.Fatalf("Request Token Phase: %s", err.Error()) + } + accessToken, err := receiveVerifier(requestToken, requestSecret) + if err != nil { + log.Fatalf("Access Token Phase: %s", err.Error()) + } + + log.Println("Consumer was granted an access token to act on behalf of a user.") + log.Printf("token: %s\nsecret: %s\n", accessToken.Token, accessToken.TokenSecret) + + ctx := context.Background() + + httpClient := config.Client(ctx, accessToken) + + // create wp-api client + client := wordpress.NewClient(&wordpress.Options{ + BaseAPIURL: "http://192.168.99.100:32777/wp-json/", + }, httpClient) + + // get the currently authenticated users details + authenticatedUser, _, err := client.Users.Me(ctx, nil) + if err != nil { + log.Fatalln(err) + } + log.Printf("Authenticated user %+v", authenticatedUser) +} + +func login() (requestToken, requestSecret string, err error) { + requestToken, requestSecret, err = config.RequestToken() + if err != nil { + return "", "", err + } + authorizationURL, err := config.AuthorizationURL(requestToken) + if err != nil { + return "", "", err + } + fmt.Printf("Open this URL in your browser:\n%s\n", authorizationURL.String()) + return requestToken, requestSecret, err +} + +func receiveVerifier(requestToken, requestSecret string) (*oauth1.Token, error) { + fmt.Printf("Choose whether to grant the application access.\nPaste " + + "the oauth_verifier parameter from the address bar: ") + var verifier string + _, err := fmt.Scanf("%s", &verifier) + accessToken, accessSecret, err := config.AccessToken(requestToken, requestSecret, verifier) + if err != nil { + return nil, err + } + return oauth1.NewToken(accessToken, accessSecret), err +} diff --git a/example/oauth2/main.go b/example/oauth2/main.go new file mode 100644 index 0000000..c2c4875 --- /dev/null +++ b/example/oauth2/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "log" + + "github.com/robbiet480/go-wordpress" + "golang.org/x/oauth2" +) + +func main() { + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: "JWT_TOKEN"}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := wordpress.NewClient(&wordpress.Options{ + BaseAPIURL: "http://192.168.99.100:32777/wp-json/", + }, tc) + + // get the currently authenticated users details + authenticatedUser, _, err := client.Users.Me(ctx, nil) + if err != nil { + log.Fatalln(err) + } + log.Printf("Authenticated user %+v", authenticatedUser) +} diff --git a/transports.go b/transports.go new file mode 100644 index 0000000..0f325c3 --- /dev/null +++ b/transports.go @@ -0,0 +1,46 @@ +package wordpress + +import "net/http" + +// BasicAuthTransport is an http.RoundTripper that authenticates all requests +// using HTTP Basic Authentication with the provided username and password. +type BasicAuthTransport struct { + Username string // WordPress username + Password string // WordPress password + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. +func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // To set extra headers, we must make a copy of the Request so + // that we don't modify the Request we were given. This is required by the + // specification of http.RoundTripper. + // + // Since we are going to modify only req.Header here, we only need a deep copy + // of req.Header. + req2 := new(http.Request) + *req2 = *req + req2.Header = make(http.Header, len(req.Header)) + for k, s := range req.Header { + req2.Header[k] = append([]string(nil), s...) + } + + req2.SetBasicAuth(t.Username, t.Password) + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using HTTP Basic Authentication. +func (t *BasicAuthTransport) Client() *http.Client { + return &http.Client{Jar: nil, Transport: t} +} + +func (t *BasicAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} From 78ee0a2fb764ea2be8d0890856342856966bddb6 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 4 Feb 2018 23:27:29 -0800 Subject: [PATCH 26/46] Remove options in favor of just a base URL + http.Client --- README.md | 8 +--- client.go | 95 ++++++++++++++++++++------------------- client_test.go | 47 +++++++++++-------- comments_test.go | 1 - discovery.go | 18 +++++--- example/basicauth/main.go | 4 +- example/oauth1/main.go | 4 +- example/oauth2/main.go | 4 +- 8 files changed, 95 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 46c2380..11ae789 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,7 @@ func main() { } // create wp-api client - client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json/` - }, tp.Client()) + client, _ := wordpress.NewClient(API_BASE_URL, tp.Client()) ctx := context.Background() @@ -119,9 +117,7 @@ func main() { } // create wp-api client - client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: API_BASE_URL, // example: `http://192.168.99.100:32777/wp-json/` - }, tp.Client()) + client, _ := wordpress.NewClient(API_BASE_URL, tp.Client()) ctx := context.Background() diff --git a/client.go b/client.go index cfd508c..02b9309 100644 --- a/client.go +++ b/client.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -20,10 +21,25 @@ import ( ) const ( + userAgent = "go-wordpress" headerTotalRecords = "X-WP-Total" headerTotalPages = "X-WP-TotalPages" ) +// ErrURLContainsWPV2 is returned from NewClient if URL contains /wp/v2. +var ErrURLContainsWPV2 = errors.New("url must not contain /wp/v2") + +// DefaultHTTPTransport is an http.RoundTripper that has DisableKeepAlives set true. +var DefaultHTTPTransport = &http.Transport{ + DisableKeepAlives: true, +} + +// DefaultHTTPClient is an http.Client with the DefaultHTTPTransport and (Cookie) Jar set nil. +var DefaultHTTPClient = &http.Client{ + Jar: nil, + Transport: DefaultHTTPTransport, +} + // Error is a generic WordPress error container. type Error struct { Response *http.Response // HTTP response that caused this error @@ -38,22 +54,13 @@ func (e *Error) Error() string { e.Response.StatusCode, e.Message) } -// Options is a struct containing options that can be passed in during client initialization. -type Options struct { - BaseAPIURL string - Location *time.Location - - // User agent used when communicating with the WordPress API. - UserAgent string -} - // Client is a struct containing values and methods used for interacting with the WordPress API. type Client struct { - client *http.Client - options *Options - BaseURL *url.URL + // User agent used when communicating with the WordPress API. + UserAgent string - common service // Reuse a single struct instead of allocating one for each service on the heap. + // WordPress timezone location + Location *time.Location Categories *CategoriesService Comments *CommentsService @@ -67,6 +74,11 @@ type Client struct { Terms *TermsService Types *TypesService Users *UsersService + + client *http.Client + baseURL *url.URL + + common service // Reuse a single struct instead of allocating one for each service on the heap. } type service struct { @@ -141,29 +153,22 @@ func (r *Response) populatePageValues() { } } -// NewClient returns an initalized Client for the given options and httpClient. -func NewClient(options *Options, httpClient *http.Client) *Client { - if strings.HasSuffix(options.BaseAPIURL, "/wp/v2") { - splitURL := strings.Split(options.BaseAPIURL, "/wp/v2") - options.BaseAPIURL = splitURL[0] +// NewClient returns an initalized Client for the given baseURL and httpClient. +func NewClient(baseURL string, httpClient *http.Client) (*Client, error) { + if strings.Contains(baseURL, "/wp/v2") { + return nil, ErrURLContainsWPV2 } - if options.Location != nil { - Location = options.Location + url, urlErr := url.Parse(baseURL) + if urlErr != nil { + return nil, urlErr } - url, _ := url.Parse(options.BaseAPIURL) - if httpClient == nil { - httpClient = &http.Client{ - Jar: nil, - Transport: &http.Transport{ - DisableKeepAlives: true, - }, - } + httpClient = DefaultHTTPClient } - c := &Client{client: httpClient, options: options, BaseURL: url} + c := &Client{client: httpClient, UserAgent: userAgent, baseURL: url} c.common.client = c c.Categories = (*CategoriesService)(&c.common) c.Comments = (*CommentsService)(&c.common) @@ -177,7 +182,7 @@ func NewClient(options *Options, httpClient *http.Client) *Client { c.Terms = (*TermsService)(&c.common) c.Types = (*TypesService)(&c.common) c.Users = (*UsersService)(&c.common) - return c + return c, nil } // addOptions adds the parameters in opt as URL query parameters to s. opt @@ -207,18 +212,18 @@ func addOptions(s string, opt interface{}) (string, error) { } // NewRequest creates an API request. A relative URL can be provided in urlStr, -// in which case it is resolved relative to the BaseURL of the Client. +// in which case it is resolved relative to the baseURL of the Client. // Relative URLs should always be specified without a preceding slash. If // specified, the value pointed to by body is JSON encoded and included as the // request body. func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { - if !strings.HasSuffix(c.BaseURL.Path, "/") { - return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL) + if !strings.HasSuffix(c.baseURL.Path, "/") { + return nil, fmt.Errorf("baseURL must have a trailing slash, but %q does not", c.baseURL) } if urlStr != "" { urlStr = fmt.Sprintf("/wp-json/wp/v2/%s", urlStr) } - u, err := c.BaseURL.Parse(urlStr) + u, err := c.baseURL.Parse(urlStr) if err != nil { return nil, err } @@ -241,8 +246,8 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ if body != nil { req.Header.Set("Content-Type", "application/json") } - if c.options.UserAgent != "" { - req.Header.Set("User-Agent", c.options.UserAgent) + if c.UserAgent != "" { + req.Header.Set("User-Agent", c.UserAgent) } return req, nil } @@ -288,7 +293,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res response := newResponse(resp) - err = CheckResponse(resp) + err = checkResponse(resp) if err != nil { // even though there was an error, we still return the response // in case the caller wants to inspect it further @@ -329,7 +334,7 @@ type RootInfo struct { // BasicInfo gets basic and publicly available information about the WordPress REST API. func (c *Client) BasicInfo(ctx context.Context) (*RootInfo, *Response, error) { var entity RootInfo - resp, err := c.Get(ctx, c.BaseURL.String(), nil, &entity) + resp, err := c.Get(ctx, c.baseURL.String(), nil, &entity) if err != nil { return &entity, resp, err } @@ -448,13 +453,13 @@ func (c *Client) PostData(ctx context.Context, urlStr string, content []byte, co return nil, closeErr } - if !strings.HasSuffix(c.BaseURL.Path, "/") { - return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL) + if !strings.HasSuffix(c.baseURL.Path, "/") { + return nil, fmt.Errorf("baseURL must have a trailing slash, but %q does not", c.baseURL) } if urlStr != "" { urlStr = fmt.Sprintf("/wp-json/wp/v2/%s", urlStr) } - u, err := c.BaseURL.Parse(urlStr) + u, err := c.baseURL.Parse(urlStr) if err != nil { return nil, err } @@ -464,8 +469,8 @@ func (c *Client) PostData(ctx context.Context, urlStr string, content []byte, co return nil, err } - if c.options.UserAgent != "" { - req.Header.Set("User-Agent", c.options.UserAgent) + if c.UserAgent != "" { + req.Header.Set("User-Agent", c.UserAgent) } req.Header.Set("Content-Type", w.FormDataContentType()) @@ -489,13 +494,13 @@ func sanitizeURL(uri *url.URL) *url.URL { return uri } -// CheckResponse checks the API response for errors, and returns them if +// checkResponse checks the API response for errors, and returns them if // present. A response is considered an error if it has a status code outside // the 200 range or equal to 202 Accepted. // API error responses are expected to have either no response // body, or a JSON response body that maps to ErrorResponse. Any other // response body will be silently ignored. -func CheckResponse(r *http.Response) error { +func checkResponse(r *http.Response) error { if c := r.StatusCode; 200 <= c && c <= 299 { return nil } diff --git a/client_test.go b/client_test.go index bd7da74..14c98b1 100644 --- a/client_test.go +++ b/client_test.go @@ -3,7 +3,7 @@ package wordpress_test import ( "context" "crypto/tls" - "net/http" + "errors" "os" "testing" @@ -15,20 +15,22 @@ var PASSWORD string = os.Getenv("WP_PASSWD") var API_BASE_URL string = os.Getenv("WP_API_URL") func TestClientNew(t *testing.T) { + // TLS verify needs to be disabled for Let'ss Encrypt for whatever reason + wordpress.DefaultHTTPTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + tp := wordpress.BasicAuthTransport{ - Username: USER, - Password: PASSWORD, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // needs to be disabled for Lets Encrypt for whatever reason - DisableKeepAlives: true, - }, + Username: USER, + Password: PASSWORD, + Transport: wordpress.DefaultHTTPTransport, } - client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: API_BASE_URL, - }, tp.Client()) + client, clientErr := wordpress.NewClient(API_BASE_URL, tp.Client()) if client == nil { t.Fatalf("Client should not be nil") } + + if clientErr != nil { + t.Fatal("Error parsing URL") + } } /** @@ -42,16 +44,23 @@ func initTestClient() (*wordpress.Client, context.Context) { panic("Please set your environment before running the tests") } + wordpress.DefaultHTTPTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + tp := wordpress.BasicAuthTransport{ - Username: USER, - Password: PASSWORD, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // needs to be disabled for Lets Encrypt for whatever reason - DisableKeepAlives: true, - }, + Username: USER, + Password: PASSWORD, + Transport: wordpress.DefaultHTTPTransport, + } + + client, clientErr := wordpress.NewClient(API_BASE_URL, tp.Client()) + + if client == nil { + panic(errors.New("client should not be nil")) + } + + if clientErr != nil { + panic(errors.New("error parsing url")) } - return wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: API_BASE_URL, - }, tp.Client()), context.Background() + return client, context.Background() } diff --git a/comments_test.go b/comments_test.go index ec9ab24..f1e09a5 100644 --- a/comments_test.go +++ b/comments_test.go @@ -112,7 +112,6 @@ func TestCommentsGet_CommentDoesNotExists(t *testing.T) { } func TestCommentsCreate(t *testing.T) { - // t.Skipf("[TestCommentsCreate] Skipped: there is an issue with creating comments, server returning empty string") wp, ctx := initTestClient() p := getAnyOnePost(t, ctx, wp) diff --git a/discovery.go b/discovery.go index d1cd377..d2105c4 100644 --- a/discovery.go +++ b/discovery.go @@ -42,19 +42,25 @@ func DiscoverAPI(baseURL string, getRootInfo bool) (*DiscoveredAPI, error) { discovered.DiscoveredURL = discoveredURL discovered.ViaHTML = true } - clientOpts := &Options{ - BaseAPIURL: discovered.DiscoveredURL, - } if getRootInfo { - client := NewClient(clientOpts, nil) + client, clientErr := NewClient(discovered.DiscoveredURL, nil) + if clientErr != nil { + return nil, clientErr + } info, _, basicInfoErr := client.BasicInfo(context.Background()) if basicInfoErr != nil { return nil, basicInfoErr } - clientOpts.Location = info.Location + client.Location = info.Location discovered.BasicInfo = info + discovered.Client = client + return discovered, nil + } + client, clientErr := NewClient(discovered.DiscoveredURL, nil) + if clientErr != nil { + return nil, clientErr } - discovered.Client = NewClient(clientOpts, nil) + discovered.Client = client return discovered, nil } diff --git a/example/basicauth/main.go b/example/basicauth/main.go index f74558d..e2c7e91 100644 --- a/example/basicauth/main.go +++ b/example/basicauth/main.go @@ -15,9 +15,7 @@ func main() { } // create wp-api client - client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: "http://192.168.99.100:32777/wp-json/", - }, tp.Client()) + client, _ := wordpress.NewClient("http://192.168.99.100:32777/wp-json/", tp.Client()) ctx := context.Background() diff --git a/example/oauth1/main.go b/example/oauth1/main.go index 635e454..0d9bc69 100644 --- a/example/oauth1/main.go +++ b/example/oauth1/main.go @@ -41,9 +41,7 @@ func main() { httpClient := config.Client(ctx, accessToken) // create wp-api client - client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: "http://192.168.99.100:32777/wp-json/", - }, httpClient) + client, _ := wordpress.NewClient("http://192.168.99.100:32777/wp-json/", httpClient) // get the currently authenticated users details authenticatedUser, _, err := client.Users.Me(ctx, nil) diff --git a/example/oauth2/main.go b/example/oauth2/main.go index c2c4875..a69badb 100644 --- a/example/oauth2/main.go +++ b/example/oauth2/main.go @@ -15,9 +15,7 @@ func main() { ) tc := oauth2.NewClient(ctx, ts) - client := wordpress.NewClient(&wordpress.Options{ - BaseAPIURL: "http://192.168.99.100:32777/wp-json/", - }, tc) + client, _ := wordpress.NewClient("http://192.168.99.100:32777/wp-json/", tc) // get the currently authenticated users details authenticatedUser, _, err := client.Users.Me(ctx, nil) From e273270b31f9c0a75e26f8df5f75ff2813e8a3a2 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sun, 4 Feb 2018 23:42:35 -0800 Subject: [PATCH 27/46] Remove _warning --- pages.go | 3 ++- posts.go | 5 +++-- utils.go | 10 ---------- 3 files changed, 5 insertions(+), 13 deletions(-) delete mode 100644 utils.go diff --git a/pages.go b/pages.go index 7d47606..875703b 100644 --- a/pages.go +++ b/pages.go @@ -3,6 +3,7 @@ package wordpress import ( "context" "fmt" + "log" "time" ) @@ -41,7 +42,7 @@ func (entity *Page) setService(c *PagesService) { func (entity *Page) Revisions() *RevisionsService { if entity.collection == nil { // missing page.collection parent. Probably Page struct was initialized manually, not fetched from API - _warning("Missing parent page collection") + log.Print("[go-wordpress] Missing parent page collection") return nil } return &RevisionsService{ diff --git a/posts.go b/posts.go index 0ee5d3e..beefe7e 100644 --- a/posts.go +++ b/posts.go @@ -3,6 +3,7 @@ package wordpress import ( "context" "fmt" + "log" "time" ) @@ -81,7 +82,7 @@ func (entity *Post) setService(c *PostsService) { func (entity *Post) Revisions() *RevisionsService { if entity.collection == nil { // missing post.collection parent. Probably Post struct was initialized manually, not fetched from API - _warning("Missing parent post collection") + log.Print("[go-wordpress] Missing parent post collection") return nil } return &RevisionsService{ @@ -96,7 +97,7 @@ func (entity *Post) Revisions() *RevisionsService { func (entity *Post) Terms() *PostsTermsService { if entity.collection == nil { // missing post.collection parent. Probably Post struct was initialized manually, not fetched from API - _warning("Missing parent post collection") + log.Print("[go-wordpress] Missing parent post collection") return nil } return &PostsTermsService{ diff --git a/utils.go b/utils.go deleted file mode 100644 index eebcb8f..0000000 --- a/utils.go +++ /dev/null @@ -1,10 +0,0 @@ -package wordpress - -import ( - "fmt" - "log" -) - -func _warning(v ...interface{}) { - log.Println(fmt.Sprintln("[go-wordpress]", v)) -} From 6bd40495c5514cc02c948a63e1c177aad7d2a45d Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 00:15:21 -0800 Subject: [PATCH 28/46] Update .gitignore --- .gitignore | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b57fd99..9d083ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a @@ -19,8 +32,6 @@ _cgo_export.* _testmain.go -*.exe -*.test *.prof credential.txt From 257a8d9b869f9c93828f60d2071cc9087f45a624 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 00:15:38 -0800 Subject: [PATCH 29/46] Fix bad struct name usage --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11ae789..3e38035 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ func main() { ctx := context.Background() - opt := &wordpress.PostsByOrgOptions{ + opt := &wordpress.PostsListOptions{ ListOptions: wordpress.ListOptions{PerPage: 10}, } // get all pages of results From 69a22169ace5ba50f826fa37d7dd37a3e390f661 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 00:15:51 -0800 Subject: [PATCH 30/46] Fix pagination --- client.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 02b9309..405afe2 100644 --- a/client.go +++ b/client.go @@ -134,21 +134,27 @@ func newResponse(r *http.Response) *Response { // populatePageValues parses the HTTP Link response headers and populates the // various pagination link values in the Response. func (r *Response) populatePageValues() { - totalRecords, _ := strconv.Atoi(r.Header.Get(headerTotalRecords)) + totalRecordsHeader := r.Header.Get(headerTotalRecords) + totalRecords, _ := strconv.Atoi(totalRecordsHeader) r.TotalRecords = totalRecords - totalPages, _ := strconv.Atoi(r.Header.Get(headerTotalPages)) + totalPagesHeader := r.Header.Get(headerTotalPages) + totalPages, _ := strconv.Atoi(totalPagesHeader) r.TotalPages = totalPages lastPage, _ := strconv.Atoi(r.Request.URL.Query().Get("page")) + if totalRecordsHeader != "" && totalPagesHeader != "" && lastPage == 0 { + lastPage = 1 + } + r.PreviousPage = lastPage r.NextPage = lastPage + 1 - if r.NextPage >= r.TotalPages { + if r.NextPage > r.TotalPages { r.NextPage = 0 } } From 3aec9d8ac85e212b6bff8be78181c2ad16fa4992 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 00:16:10 -0800 Subject: [PATCH 31/46] Remove TLS skip verify --- client_test.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/client_test.go b/client_test.go index 14c98b1..9947e99 100644 --- a/client_test.go +++ b/client_test.go @@ -2,7 +2,6 @@ package wordpress_test import ( "context" - "crypto/tls" "errors" "os" "testing" @@ -15,13 +14,10 @@ var PASSWORD string = os.Getenv("WP_PASSWD") var API_BASE_URL string = os.Getenv("WP_API_URL") func TestClientNew(t *testing.T) { - // TLS verify needs to be disabled for Let'ss Encrypt for whatever reason - wordpress.DefaultHTTPTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} tp := wordpress.BasicAuthTransport{ - Username: USER, - Password: PASSWORD, - Transport: wordpress.DefaultHTTPTransport, + Username: USER, + Password: PASSWORD, } client, clientErr := wordpress.NewClient(API_BASE_URL, tp.Client()) if client == nil { @@ -44,12 +40,9 @@ func initTestClient() (*wordpress.Client, context.Context) { panic("Please set your environment before running the tests") } - wordpress.DefaultHTTPTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - tp := wordpress.BasicAuthTransport{ - Username: USER, - Password: PASSWORD, - Transport: wordpress.DefaultHTTPTransport, + Username: USER, + Password: PASSWORD, } client, clientErr := wordpress.NewClient(API_BASE_URL, tp.Client()) From bb882969d4d9bf39dd5c6b11e0a7fce90a2d0f21 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 00:16:20 -0800 Subject: [PATCH 32/46] log.Print -> log.Println --- pages.go | 2 +- posts.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pages.go b/pages.go index 875703b..5747f1b 100644 --- a/pages.go +++ b/pages.go @@ -42,7 +42,7 @@ func (entity *Page) setService(c *PagesService) { func (entity *Page) Revisions() *RevisionsService { if entity.collection == nil { // missing page.collection parent. Probably Page struct was initialized manually, not fetched from API - log.Print("[go-wordpress] Missing parent page collection") + log.Println("[go-wordpress] Missing parent page collection") return nil } return &RevisionsService{ diff --git a/posts.go b/posts.go index beefe7e..273ef93 100644 --- a/posts.go +++ b/posts.go @@ -82,7 +82,7 @@ func (entity *Post) setService(c *PostsService) { func (entity *Post) Revisions() *RevisionsService { if entity.collection == nil { // missing post.collection parent. Probably Post struct was initialized manually, not fetched from API - log.Print("[go-wordpress] Missing parent post collection") + log.Println("[go-wordpress] Missing parent post collection") return nil } return &RevisionsService{ @@ -97,7 +97,7 @@ func (entity *Post) Revisions() *RevisionsService { func (entity *Post) Terms() *PostsTermsService { if entity.collection == nil { // missing post.collection parent. Probably Post struct was initialized manually, not fetched from API - log.Print("[go-wordpress] Missing parent post collection") + log.Println("[go-wordpress] Missing parent post collection") return nil } return &PostsTermsService{ From 073fdafcacd5581e5995c47f5c8a1c1e9713206a Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 01:13:44 -0800 Subject: [PATCH 33/46] Improve comments on struct fields --- client.go | 8 ++++++-- posts.go | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/client.go b/client.go index 405afe2..e0b9606 100644 --- a/client.go +++ b/client.go @@ -94,9 +94,13 @@ type ListOptions struct { // For paginated result sets, the number of results to include per page. PerPage int `url:"per_page,omitempty"` - Offset int `url:"offset,omitempty"` - Order string `url:"order,omitempty"` + // Offset the result set by a specific number of items. + Offset int `url:"offset,omitempty"` + // Order sort attribute ascending or descending. + Order string `url:"order,omitempty"` + // Sort collection by object attribute. OrderBy string `url:"orderby,omitempty"` + // Scope under which the request is made; determines fields present in response. Context string `url:"context,omitempty"` } diff --git a/posts.go b/posts.go index 273ef93..c7067e3 100644 --- a/posts.go +++ b/posts.go @@ -118,20 +118,20 @@ type PostsService service // PostsListOptions are options that can be passed to List(). type PostsListOptions struct { - After *time.Time `url:"after,omitempty"` - Author int `url:"author,omitempty"` - AuthorExclude []int `url:"author_exclude,omitempty"` - Before *time.Time `url:"before,omitempty"` - Categories []int `url:"categories,omitempty"` - CategoriesExclude []int `url:"categories_exclude,omitempty"` - Exclude []int `url:"exclude,omitempty"` - Include []int `url:"include,omitempty"` - Search string `url:"search,omitempty"` - Slug string `url:"slug,omitempty"` - Status string `url:"status,omitempty"` - Sticky bool `url:"sticky,omitempty"` - Tags []int `url:"tags,omitempty"` - TagsExclude []int `url:"tags_exclude,omitempty"` + After *time.Time `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty"` // Limit result set to posts assigned to specific authors. + AuthorExclude []int `url:"author_exclude,omitempty"` // Ensure result set excludes posts assigned to specific authors. + Before *time.Time `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. + Categories []int `url:"categories,omitempty"` // Limit result set to posts with given category IDs. + CategoriesExclude []int `url:"categories_exclude,omitempty"` // Ensure result set excludes posts with given category IDs. + Exclude []int `url:"exclude,omitempty"` // Ensure result set excludes specific IDs. + Include []int `url:"include,omitempty"` // Limit result set to specific IDs. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Slug string `url:"slug,omitempty"` // An alphanumeric identifier for the post type. + Status string `url:"status,omitempty"` // A named status for the object. + Sticky bool `url:"sticky,omitempty"` // Whether or not the object should be treated as sticky. + Tags []int `url:"tags,omitempty"` // Limit result set to posts with given tag IDs. + TagsExclude []int `url:"tags_exclude,omitempty"` // Ensure result set excludes posts with given tag IDs. ListOptions } From 638b589526d15149be7c85b51b15c76e3fcd1fe4 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 01:14:03 -0800 Subject: [PATCH 34/46] Make error data a struct --- client.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index e0b9606..870dd77 100644 --- a/client.go +++ b/client.go @@ -45,7 +45,10 @@ type Error struct { Response *http.Response // HTTP response that caused this error Code string `json:"code"` Message string `json:"message"` - Data interface{} `json:"data"` + Data struct { + Status int `json:"status"` + Params map[string]string `json:"params"` + } `json:"data"` } func (e *Error) Error() string { From 4403ab35f4443e7ea76797ab2b5c084783988a1a Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 01:17:58 -0800 Subject: [PATCH 35/46] Encode slices in URLs with brackets --- categories.go | 4 ++-- comments.go | 10 +++++----- media.go | 12 ++++++------ pages.go | 14 +++++++------- posts.go | 28 ++++++++++++++-------------- tags.go | 4 ++-- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/categories.go b/categories.go index 4550b5a..18dc6d6 100644 --- a/categories.go +++ b/categories.go @@ -22,8 +22,8 @@ type CategoriesService service // CategoriesListOptions are options that can be passed to List(). type CategoriesListOptions struct { - Exclude []int `url:"exclude,omitempty"` - Include []int `url:"include,omitempty"` + Exclude []int `url:"exclude,omitempty,brackets"` + Include []int `url:"include,omitempty,brackets"` Parent int `url:"parent,omitempty"` Post int `url:"post,omitempty"` Search string `url:"search,omitempty"` diff --git a/comments.go b/comments.go index 14ce8bc..7d504f6 100644 --- a/comments.go +++ b/comments.go @@ -35,12 +35,12 @@ type CommentsService service type CommentsListOptions struct { After *time.Time `url:"after,omitempty"` Author int `url:"author,omitempty"` - AuthorExclude []int `url:"author_exclude,omitempty"` + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` Before *time.Time `url:"before,omitempty"` - Exclude []int `url:"exclude,omitempty"` - Include []int `url:"include,omitempty"` - Parent []int `url:"parent,omitempty"` - ParentExclude []int `url:"parent_exclude,omitempty"` + Exclude []int `url:"exclude,omitempty,brackets"` + Include []int `url:"include,omitempty,brackets"` + Parent []int `url:"parent,omitempty,brackets"` + ParentExclude []int `url:"parent_exclude,omitempty,brackets"` Password string `url:"password,omitempty"` Post int `url:"post,omitempty"` Search string `url:"search,omitempty"` diff --git a/media.go b/media.go index bec5ae7..f8b361b 100644 --- a/media.go +++ b/media.go @@ -74,15 +74,15 @@ type MediaService service // MediasListOptions are options that can be passed to List(). type MediasListOptions struct { After *time.Time `url:"after,omitempty"` - Author []int `url:"author,omitempty"` - AuthorExclude []int `url:"author_exclude,omitempty"` + Author []int `url:"author,omitempty,brackets"` + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` Before *time.Time `url:"before,omitempty"` - Exclude []int `url:"exclude,omitempty"` - Include []int `url:"include,omitempty"` + Exclude []int `url:"exclude,omitempty,brackets"` + Include []int `url:"include,omitempty,brackets"` MediaType string `url:"media_type,omitempty"` MimeType string `url:"mime_type,omitempty"` - Parent []int `url:"parent,omitempty"` - ParentExclude []int `url:"parent_exclude,omitempty"` + Parent []int `url:"parent,omitempty,brackets"` + ParentExclude []int `url:"parent_exclude,omitempty,brackets"` Search string `url:"search,omitempty"` Slug string `url:"slug,omitempty"` Status string `url:"status,omitempty"` diff --git a/pages.go b/pages.go index 5747f1b..433d4b6 100644 --- a/pages.go +++ b/pages.go @@ -65,18 +65,18 @@ type PagesService service type PagesListOptions struct { After *time.Time `url:"after,omitempty"` Author int `url:"author,omitempty"` - AuthorExclude []int `url:"author_exclude,omitempty"` + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` Before *time.Time `url:"before,omitempty"` - Categories []int `url:"categories,omitempty"` - CategoriesExclude []int `url:"categories_exclude,omitempty"` - Exclude []int `url:"exclude,omitempty"` - Include []int `url:"include,omitempty"` + Categories []int `url:"categories,omitempty,brackets"` + CategoriesExclude []int `url:"categories_exclude,omitempty,brackets"` + Exclude []int `url:"exclude,omitempty,brackets"` + Include []int `url:"include,omitempty,brackets"` Search string `url:"search,omitempty"` Slug string `url:"slug,omitempty"` Status string `url:"status,omitempty"` Sticky bool `url:"sticky,omitempty"` - Tags []int `url:"tags,omitempty"` - TagsExclude []int `url:"tags_exclude,omitempty"` + Tags []int `url:"tags,omitempty,brackets"` + TagsExclude []int `url:"tags_exclude,omitempty,brackets"` ListOptions } diff --git a/posts.go b/posts.go index c7067e3..31dc50b 100644 --- a/posts.go +++ b/posts.go @@ -118,20 +118,20 @@ type PostsService service // PostsListOptions are options that can be passed to List(). type PostsListOptions struct { - After *time.Time `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. - Author []int `url:"author,omitempty"` // Limit result set to posts assigned to specific authors. - AuthorExclude []int `url:"author_exclude,omitempty"` // Ensure result set excludes posts assigned to specific authors. - Before *time.Time `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. - Categories []int `url:"categories,omitempty"` // Limit result set to posts with given category IDs. - CategoriesExclude []int `url:"categories_exclude,omitempty"` // Ensure result set excludes posts with given category IDs. - Exclude []int `url:"exclude,omitempty"` // Ensure result set excludes specific IDs. - Include []int `url:"include,omitempty"` // Limit result set to specific IDs. - Search string `url:"search,omitempty"` // Limit results to those matching a string. - Slug string `url:"slug,omitempty"` // An alphanumeric identifier for the post type. - Status string `url:"status,omitempty"` // A named status for the object. - Sticky bool `url:"sticky,omitempty"` // Whether or not the object should be treated as sticky. - Tags []int `url:"tags,omitempty"` // Limit result set to posts with given tag IDs. - TagsExclude []int `url:"tags_exclude,omitempty"` // Ensure result set excludes posts with given tag IDs. + After *time.Time `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. + Before *time.Time `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. + Categories []int `url:"categories,omitempty,brackets"` // Limit result set to posts with given category IDs. + CategoriesExclude []int `url:"categories_exclude,omitempty,brackets"` // Ensure result set excludes posts with given category IDs. + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Slug string `url:"slug,omitempty"` // An alphanumeric identifier for the post type. + Status string `url:"status,omitempty"` // A named status for the object. + Sticky bool `url:"sticky,omitempty"` // Whether or not the object should be treated as sticky. + Tags []int `url:"tags,omitempty,brackets"` // Limit result set to posts with given tag IDs. + TagsExclude []int `url:"tags_exclude,omitempty,brackets"` // Ensure result set excludes posts with given tag IDs. ListOptions } diff --git a/tags.go b/tags.go index ce7e971..9746535 100644 --- a/tags.go +++ b/tags.go @@ -21,9 +21,9 @@ type TagsService service // TagsListOptions are options that can be passed to List(). type TagsListOptions struct { - Exclude []int `url:"exclude,omitempty"` + Exclude []int `url:"exclude,omitempty,brackets"` HideEmpty bool `url:"hide_empty,omitempty"` - Include []int `url:"include,omitempty"` + Include []int `url:"include,omitempty,brackets"` Parent int `url:"parent,omitempty"` Post int `url:"post,omitempty"` Search string `url:"search,omitempty"` From c542a2205b597df8ed1eff66409f9b69e3ddf380 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 02:47:27 -0800 Subject: [PATCH 36/46] Dynamically generate structs with pretty comments and all possible props w/ correct types --- README.md | 4 +- categories.go | 14 +- comments.go | 22 +- list_options.go | 129 ++++++ media.go | 22 +- pages.go | 23 +- pages_test.go | 2 +- posts.go | 23 +- posts_test.go | 4 +- tags.go | 15 +- wp_api_json_schema.json | 858 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 998 insertions(+), 118 deletions(-) create mode 100644 list_options.go create mode 100644 wp_api_json_schema.json diff --git a/README.md b/README.md index 3e38035..391e8a4 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ All requests for resource collections (posts, pages, media, revisions, etc.) support pagination. Pagination options are described in the `wordpress.ListOptions` struct and passed to the list methods directly or as an embedded type of a more specific list options struct (for example -`wordpress.PostsListOptions`). Pages information is available via the +`wordpress.PostListOptions`). Pages information is available via the `wordpress.Response` struct. ```go @@ -121,7 +121,7 @@ func main() { ctx := context.Background() - opt := &wordpress.PostsListOptions{ + opt := &wordpress.PostListOptions{ ListOptions: wordpress.ListOptions{PerPage: 10}, } // get all pages of results diff --git a/categories.go b/categories.go index 18dc6d6..25892ee 100644 --- a/categories.go +++ b/categories.go @@ -20,20 +20,8 @@ type Category struct { // CategoriesService provides access to the category related functions in the WordPress REST API. type CategoriesService service -// CategoriesListOptions are options that can be passed to List(). -type CategoriesListOptions struct { - Exclude []int `url:"exclude,omitempty,brackets"` - Include []int `url:"include,omitempty,brackets"` - Parent int `url:"parent,omitempty"` - Post int `url:"post,omitempty"` - Search string `url:"search,omitempty"` - Slug string `url:"slug,omitempty"` - - ListOptions -} - // List returns a list of categories. -func (c *CategoriesService) List(ctx context.Context, opts *CategoriesListOptions) ([]*Category, *Response, error) { +func (c *CategoriesService) List(ctx context.Context, opts *CategoryListOptions) ([]*Category, *Response, error) { u, err := addOptions("categories", opts) if err != nil { return nil, nil, err diff --git a/comments.go b/comments.go index 7d504f6..d1ecaac 100644 --- a/comments.go +++ b/comments.go @@ -3,7 +3,6 @@ package wordpress import ( "context" "fmt" - "time" ) // Comment represents a WordPress post comment. @@ -31,27 +30,8 @@ type Comment struct { // CommentsService provides access to the comment related functions in the WordPress REST API. type CommentsService service -// CommentsListOptions are options that can be passed to List(). -type CommentsListOptions struct { - After *time.Time `url:"after,omitempty"` - Author int `url:"author,omitempty"` - AuthorExclude []int `url:"author_exclude,omitempty,brackets"` - Before *time.Time `url:"before,omitempty"` - Exclude []int `url:"exclude,omitempty,brackets"` - Include []int `url:"include,omitempty,brackets"` - Parent []int `url:"parent,omitempty,brackets"` - ParentExclude []int `url:"parent_exclude,omitempty,brackets"` - Password string `url:"password,omitempty"` - Post int `url:"post,omitempty"` - Search string `url:"search,omitempty"` - Status string `url:"status,omitempty"` - Type string `url:"type,omitempty"` - - ListOptions -} - // List returns a list of comments. -func (c *CommentsService) List(ctx context.Context, opts *CommentsListOptions) ([]*Comment, *Response, error) { +func (c *CommentsService) List(ctx context.Context, opts *CommentListOptions) ([]*Comment, *Response, error) { u, err := addOptions("comments", opts) if err != nil { return nil, nil, err diff --git a/list_options.go b/list_options.go new file mode 100644 index 0000000..0c4d178 --- /dev/null +++ b/list_options.go @@ -0,0 +1,129 @@ +//go:generate schema-generate -o list_options.go -p wordpress wp_api_json_schema.json +// must use https://github.com/robbiet480/generate for comments + url tag + brackets +// Code generated by schema-generate. DO NOT EDIT. + +package wordpress + +// CategoryListOptions are options that can be passed to List(). +type CategoryListOptions struct { + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + HideEmpty bool `url:"hide_empty,omitempty"` // Whether to hide terms not assigned to any posts. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + Parent int `url:"parent,omitempty"` // Limit result set to terms assigned to a specific parent. + Post int `url:"post,omitempty"` // Limit result set to terms assigned to a specific post. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to terms with one or more specific slugs. + + ListOptions +} + +// CommentListOptions are options that can be passed to List(). +type CommentListOptions struct { + After string `url:"after,omitempty"` // Limit response to comments published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to comments assigned to specific user IDs. Requires authorization. + AuthorEmail string `url:"author_email,omitempty"` // Limit result set to that from a specific author email. Requires authorization. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes comments assigned to specific user IDs. Requires authorization. + Before string `url:"before,omitempty"` // Limit response to comments published before a given ISO8601 compliant date. + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. + Parent []int `url:"parent,omitempty,brackets"` // Limit result set to comments of specific parent IDs. + ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Ensure result set excludes specific parent IDs. + Password string `url:"password,omitempty"` // The password for the post if it is password protected. + Post []int `url:"post,omitempty,brackets"` // Limit result set to comments assigned to specific post IDs. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Status string `url:"status,omitempty"` // Limit result set to comments assigned a specific status. Requires authorization. + Type string `url:"type,omitempty"` // Limit result set to comments assigned a specific type. Requires authorization. + + ListOptions +} + +// MediaListOptions are options that can be passed to List(). +type MediaListOptions struct { + After string `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. + Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + MediaType string `url:"media_type,omitempty"` // Limit result set to attachments of a particular media type. + MimeType string `url:"mime_type,omitempty"` // Limit result set to attachments of a particular MIME type. + Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. + Parent []int `url:"parent,omitempty,brackets"` // Limit result set to items with particular parent IDs. + ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Limit result set to all items except those of a particular parent ID. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. + Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. + + ListOptions +} + +// PageListOptions are options that can be passed to List(). +type PageListOptions struct { + After string `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. + Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + MenuOrder int `url:"menu_order,omitempty"` // Limit result set to posts with a specific menu_order value. + Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. + Parent []int `url:"parent,omitempty,brackets"` // Limit result set to items with particular parent IDs. + ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Limit result set to all items except those of a particular parent ID. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. + Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. + + ListOptions +} + +// PostListOptions are options that can be passed to List(). +type PostListOptions struct { + After string `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. + Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. + Categories []int `url:"categories,omitempty,brackets"` // Limit result set to all items that have the specified term assigned in the categories taxonomy. + CategoriesExclude []int `url:"categories_exclude,omitempty,brackets"` // Limit result set to all items except those that have the specified term assigned in the categories taxonomy. + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. + Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. + Sticky bool `url:"sticky,omitempty"` // Limit result set to items that are sticky. + Tags []int `url:"tags,omitempty,brackets"` // Limit result set to all items that have the specified term assigned in the tags taxonomy. + TagsExclude []int `url:"tags_exclude,omitempty,brackets"` // Limit result set to all items except those that have the specified term assigned in the tags taxonomy. + + ListOptions +} + +// TagListOptions are options that can be passed to List(). +type TagListOptions struct { + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + HideEmpty bool `url:"hide_empty,omitempty"` // Whether to hide terms not assigned to any posts. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. + Post int `url:"post,omitempty"` // Limit result set to terms assigned to a specific post. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to terms with one or more specific slugs. + + ListOptions +} + +// UserListOptions are options that can be passed to List(). +type UserListOptions struct { + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. + Roles []string `url:"roles,omitempty,brackets"` // Limit result set to users matching at least one specific role provided. Accepts csv list or single role. + Search string `url:"search,omitempty"` // Limit results to those matching a string. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to users with one or more specific slugs. + + ListOptions +} + +// WordPressRESTAPI +type WordPressRESTAPI struct { + ListOptions +} diff --git a/media.go b/media.go index f8b361b..02aee9a 100644 --- a/media.go +++ b/media.go @@ -3,7 +3,6 @@ package wordpress import ( "context" "fmt" - "time" ) // MediaDetailsSizesItem provides details for a single media item's size. @@ -71,27 +70,8 @@ type Media struct { // MediaService provides access to the media related functions in the WordPress REST API. type MediaService service -// MediasListOptions are options that can be passed to List(). -type MediasListOptions struct { - After *time.Time `url:"after,omitempty"` - Author []int `url:"author,omitempty,brackets"` - AuthorExclude []int `url:"author_exclude,omitempty,brackets"` - Before *time.Time `url:"before,omitempty"` - Exclude []int `url:"exclude,omitempty,brackets"` - Include []int `url:"include,omitempty,brackets"` - MediaType string `url:"media_type,omitempty"` - MimeType string `url:"mime_type,omitempty"` - Parent []int `url:"parent,omitempty,brackets"` - ParentExclude []int `url:"parent_exclude,omitempty,brackets"` - Search string `url:"search,omitempty"` - Slug string `url:"slug,omitempty"` - Status string `url:"status,omitempty"` - - ListOptions -} - // List returns a list of medias. -func (c *MediaService) List(ctx context.Context, opts *MediasListOptions) ([]*Media, *Response, error) { +func (c *MediaService) List(ctx context.Context, opts *MediaListOptions) ([]*Media, *Response, error) { u, err := addOptions("media", opts) if err != nil { return nil, nil, err diff --git a/pages.go b/pages.go index 433d4b6..9710180 100644 --- a/pages.go +++ b/pages.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "time" ) // Page represents a WordPress page. @@ -61,28 +60,8 @@ func (entity *Page) Populate(ctx context.Context, params interface{}) (*Page, *R // PagesService provides access to the page related functions in the WordPress REST API. type PagesService service -// PagesListOptions are options that can be passed to List(). -type PagesListOptions struct { - After *time.Time `url:"after,omitempty"` - Author int `url:"author,omitempty"` - AuthorExclude []int `url:"author_exclude,omitempty,brackets"` - Before *time.Time `url:"before,omitempty"` - Categories []int `url:"categories,omitempty,brackets"` - CategoriesExclude []int `url:"categories_exclude,omitempty,brackets"` - Exclude []int `url:"exclude,omitempty,brackets"` - Include []int `url:"include,omitempty,brackets"` - Search string `url:"search,omitempty"` - Slug string `url:"slug,omitempty"` - Status string `url:"status,omitempty"` - Sticky bool `url:"sticky,omitempty"` - Tags []int `url:"tags,omitempty,brackets"` - TagsExclude []int `url:"tags_exclude,omitempty,brackets"` - - ListOptions -} - // List returns a list of pages. -func (c *PagesService) List(ctx context.Context, opts *PagesListOptions) ([]*Page, *Response, error) { +func (c *PagesService) List(ctx context.Context, opts *PageListOptions) ([]*Page, *Response, error) { u, err := addOptions("pages", opts) if err != nil { return nil, nil, err diff --git a/pages_test.go b/pages_test.go index eb9667b..4d8c370 100644 --- a/pages_test.go +++ b/pages_test.go @@ -86,7 +86,7 @@ func TestPagesList_WithParamsString(t *testing.T) { wp, ctx := initTestClient() // assumes that API user authenticated with `edit_pages` - pages, resp, err := wp.Pages.List(ctx, &wordpress.PagesListOptions{Status: "draft"}) + pages, resp, err := wp.Pages.List(ctx, &wordpress.PageListOptions{Status: "draft"}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } diff --git a/posts.go b/posts.go index 31dc50b..bd69fcb 100644 --- a/posts.go +++ b/posts.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "time" ) // Constants for different post values. @@ -116,28 +115,8 @@ func (entity *Post) Populate(ctx context.Context, params interface{}) (*Post, *R // PostsService provides access to the post related functions in the WordPress REST API. type PostsService service -// PostsListOptions are options that can be passed to List(). -type PostsListOptions struct { - After *time.Time `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. - Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. - AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. - Before *time.Time `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. - Categories []int `url:"categories,omitempty,brackets"` // Limit result set to posts with given category IDs. - CategoriesExclude []int `url:"categories_exclude,omitempty,brackets"` // Ensure result set excludes posts with given category IDs. - Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. - Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. - Search string `url:"search,omitempty"` // Limit results to those matching a string. - Slug string `url:"slug,omitempty"` // An alphanumeric identifier for the post type. - Status string `url:"status,omitempty"` // A named status for the object. - Sticky bool `url:"sticky,omitempty"` // Whether or not the object should be treated as sticky. - Tags []int `url:"tags,omitempty,brackets"` // Limit result set to posts with given tag IDs. - TagsExclude []int `url:"tags_exclude,omitempty,brackets"` // Ensure result set excludes posts with given tag IDs. - - ListOptions -} - // List returns a list of posts. -func (c *PostsService) List(ctx context.Context, opts *PostsListOptions) ([]*Post, *Response, error) { +func (c *PostsService) List(ctx context.Context, opts *PostListOptions) ([]*Post, *Response, error) { u, err := addOptions("posts", opts) if err != nil { return nil, nil, err diff --git a/posts_test.go b/posts_test.go index 252f232..fb0ea57 100644 --- a/posts_test.go +++ b/posts_test.go @@ -87,7 +87,7 @@ func TestPostsList_WithParamsString(t *testing.T) { wp, ctx := initTestClient() // assumes that API user authenticated with `edit_posts` - posts, resp, err := wp.Posts.List(ctx, &wordpress.PostsListOptions{Status: "draft"}) + posts, resp, err := wp.Posts.List(ctx, &wordpress.PostListOptions{Status: "draft"}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } @@ -98,7 +98,7 @@ func TestPostsList_WithParamsString(t *testing.T) { if len(posts) != 0 { t.Errorf("Should return zero draft posts, returned %v", len(posts)) } - posts, resp, err = wp.Posts.List(ctx, &wordpress.PostsListOptions{Status: "publish"}) + posts, resp, err = wp.Posts.List(ctx, &wordpress.PostListOptions{Status: "publish"}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } diff --git a/tags.go b/tags.go index 9746535..b8f44b7 100644 --- a/tags.go +++ b/tags.go @@ -19,21 +19,8 @@ type Tag struct { // TagsService provides access to the Tag related functions in the WordPress REST API. type TagsService service -// TagsListOptions are options that can be passed to List(). -type TagsListOptions struct { - Exclude []int `url:"exclude,omitempty,brackets"` - HideEmpty bool `url:"hide_empty,omitempty"` - Include []int `url:"include,omitempty,brackets"` - Parent int `url:"parent,omitempty"` - Post int `url:"post,omitempty"` - Search string `url:"search,omitempty"` - Slug string `url:"slug,omitempty"` - - ListOptions -} - // List returns a list of tags. -func (c *TagsService) List(ctx context.Context, opts *TagsListOptions) ([]*Tag, *Response, error) { +func (c *TagsService) List(ctx context.Context, opts *TagListOptions) ([]*Tag, *Response, error) { u, err := addOptions("tags", opts) if err != nil { return nil, nil, err diff --git a/wp_api_json_schema.json b/wp_api_json_schema.json new file mode 100644 index 0000000..048ab05 --- /dev/null +++ b/wp_api_json_schema.json @@ -0,0 +1,858 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "title": "WordPress REST API", + "definitions": { + "UsersListOptions": { + "properties": { + "context": { + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "default": 10, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string" + }, + "exclude": { + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer" + }, + "order": { + "default": "asc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "default": "name", + "enum": [ + "id", + "include", + "name", + "registered_date", + "slug", + "include_slugs", + "email", + "url" + ], + "description": "Sort collection by object attribute.", + "type": "string" + }, + "slug": { + "description": "Limit result set to users with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + } + }, + "roles": { + "description": "Limit result set to users matching at least one specific role provided. Accepts csv list or single role.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PagesListOptions": { + "properties": { + "context": { + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "default": 10, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string" + }, + "after": { + "description": "Limit response to posts published after a given ISO8601 compliant date.", + "type": "string" + }, + "author": { + "default": [], + "description": "Limit result set to posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + } + }, + "author_exclude": { + "default": [], + "description": "Ensure result set excludes posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + } + }, + "before": { + "description": "Limit response to posts published before a given ISO8601 compliant date.", + "type": "string" + }, + "exclude": { + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "menu_order": { + "description": "Limit result set to posts with a specific menu_order value.", + "type": "integer" + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer" + }, + "order": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "default": "date", + "enum": [ + "author", + "date", + "id", + "include", + "modified", + "parent", + "relevance", + "slug", + "include_slugs", + "title", + "menu_order" + ], + "description": "Sort collection by object attribute.", + "type": "string" + }, + "parent": { + "default": [], + "description": "Limit result set to items with particular parent IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "parent_exclude": { + "default": [], + "description": "Limit result set to all items except those of a particular parent ID.", + "type": "array", + "items": { + "type": "integer" + } + }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "default": "publish", + "description": "Limit result set to posts assigned one or more statuses.", + "type": "array", + "items": { + "enum": [ + "publish", + "future", + "draft", + "pending", + "private", + "trash", + "auto-draft", + "inherit", + "any" + ], + "type": "string" + } + } + } + }, + "TagsListOptions": { + "properties": { + "context": { + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "default": 10, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string" + }, + "exclude": { + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer" + }, + "order": { + "default": "asc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "default": "name", + "enum": [ + "id", + "include", + "name", + "slug", + "include_slugs", + "term_group", + "description", + "count" + ], + "description": "Sort collection by term attribute.", + "type": "string" + }, + "hide_empty": { + "default": false, + "description": "Whether to hide terms not assigned to any posts.", + "type": "boolean" + }, + "post": { + "description": "Limit result set to terms assigned to a specific post.", + "type": "integer" + }, + "slug": { + "description": "Limit result set to terms with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CategoriesListOptions": { + "properties": { + "context": { + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "default": 10, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string" + }, + "exclude": { + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "order": { + "default": "asc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "default": "name", + "enum": [ + "id", + "include", + "name", + "slug", + "include_slugs", + "term_group", + "description", + "count" + ], + "description": "Sort collection by term attribute.", + "type": "string" + }, + "hide_empty": { + "default": false, + "description": "Whether to hide terms not assigned to any posts.", + "type": "boolean" + }, + "parent": { + "description": "Limit result set to terms assigned to a specific parent.", + "type": "integer" + }, + "post": { + "description": "Limit result set to terms assigned to a specific post.", + "type": "integer" + }, + "slug": { + "description": "Limit result set to terms with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CommentsListOptions": { + "properties": { + "context": { + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "default": 10, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string" + }, + "after": { + "description": "Limit response to comments published after a given ISO8601 compliant date.", + "type": "string" + }, + "author": { + "description": "Limit result set to comments assigned to specific user IDs. Requires authorization.", + "type": "array", + "items": { + "type": "integer" + } + }, + "author_exclude": { + "description": "Ensure result set excludes comments assigned to specific user IDs. Requires authorization.", + "type": "array", + "items": { + "type": "integer" + } + }, + "author_email": { + "description": "Limit result set to that from a specific author email. Requires authorization.", + "type": "string" + }, + "before": { + "description": "Limit response to comments published before a given ISO8601 compliant date.", + "type": "string" + }, + "exclude": { + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer" + }, + "order": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "default": "date_gmt", + "enum": [ + "date", + "date_gmt", + "id", + "include", + "post", + "parent", + "type" + ], + "description": "Sort collection by object attribute.", + "type": "string" + }, + "parent": { + "default": [], + "description": "Limit result set to comments of specific parent IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "parent_exclude": { + "default": [], + "description": "Ensure result set excludes specific parent IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "post": { + "default": [], + "description": "Limit result set to comments assigned to specific post IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "status": { + "default": "approve", + "description": "Limit result set to comments assigned a specific status. Requires authorization.", + "type": "string" + }, + "type": { + "default": "comment", + "description": "Limit result set to comments assigned a specific type. Requires authorization.", + "type": "string" + }, + "password": { + "description": "The password for the post if it is password protected.", + "type": "string" + } + } + }, + "MediaListOptions": { + "properties": { + "context": { + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "default": 10, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string" + }, + "after": { + "description": "Limit response to posts published after a given ISO8601 compliant date.", + "type": "string" + }, + "author": { + "default": [], + "description": "Limit result set to posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + } + }, + "author_exclude": { + "default": [], + "description": "Ensure result set excludes posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + } + }, + "before": { + "description": "Limit response to posts published before a given ISO8601 compliant date.", + "type": "string" + }, + "exclude": { + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer" + }, + "order": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "default": "date", + "enum": [ + "author", + "date", + "id", + "include", + "modified", + "parent", + "relevance", + "slug", + "include_slugs", + "title" + ], + "description": "Sort collection by object attribute.", + "type": "string" + }, + "parent": { + "default": [], + "description": "Limit result set to items with particular parent IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "parent_exclude": { + "default": [], + "description": "Limit result set to all items except those of a particular parent ID.", + "type": "array", + "items": { + "type": "integer" + } + }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "default": "inherit", + "description": "Limit result set to posts assigned one or more statuses.", + "type": "array", + "items": { + "enum": [ + "inherit", + "private", + "trash" + ], + "type": "string" + } + }, + "media_type": { + "enum": [ + "image", + "video", + "text", + "application", + "audio" + ], + "description": "Limit result set to attachments of a particular media type.", + "type": "string" + }, + "mime_type": { + "description": "Limit result set to attachments of a particular MIME type.", + "type": "string" + } + } + }, + "PostsListOptions": { + "properties": { + "context": { + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "default": 10, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string" + }, + "after": { + "description": "Limit response to posts published after a given ISO8601 compliant date.", + "type": "string" + }, + "author": { + "default": [], + "description": "Limit result set to posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + } + }, + "author_exclude": { + "default": [], + "description": "Ensure result set excludes posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + } + }, + "before": { + "description": "Limit response to posts published before a given ISO8601 compliant date.", + "type": "string" + }, + "exclude": { + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer" + }, + "order": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "default": "date", + "enum": [ + "author", + "date", + "id", + "include", + "modified", + "parent", + "relevance", + "slug", + "include_slugs", + "title" + ], + "description": "Sort collection by object attribute.", + "type": "string" + }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "default": "publish", + "description": "Limit result set to posts assigned one or more statuses.", + "type": "array", + "items": { + "enum": [ + "publish", + "future", + "draft", + "pending", + "private", + "trash", + "auto-draft", + "inherit", + "any" + ], + "type": "string" + } + }, + "categories": { + "default": [], + "description": "Limit result set to all items that have the specified term assigned in the categories taxonomy.", + "type": "array", + "items": { + "type": "integer" + } + }, + "categories_exclude": { + "default": [], + "description": "Limit result set to all items except those that have the specified term assigned in the categories taxonomy.", + "type": "array", + "items": { + "type": "integer" + } + }, + "tags": { + "default": [], + "description": "Limit result set to all items that have the specified term assigned in the tags taxonomy.", + "type": "array", + "items": { + "type": "integer" + } + }, + "tags_exclude": { + "default": [], + "description": "Limit result set to all items except those that have the specified term assigned in the tags taxonomy.", + "type": "array", + "items": { + "type": "integer" + } + }, + "sticky": { + "description": "Limit result set to items that are sticky.", + "type": "boolean" + } + } + } + } +} From d24ae11a46f23c1b79a60db3b5ef4017fedc6b5d Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 02:55:16 -0800 Subject: [PATCH 37/46] Users uses ListOptions now --- users.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/users.go b/users.go index a4d897a..c2524a2 100644 --- a/users.go +++ b/users.go @@ -5,7 +5,7 @@ import ( "fmt" ) -// AvatarURLS returns sizes of the users avatar. +// AvatarURLS returns different sizes of the users avatar. type AvatarURLS struct { Size24 string `json:"24,omitempty"` Size48 string `json:"48,omitempty"` @@ -47,10 +47,24 @@ func (c *UsersService) Me(ctx context.Context, params interface{}) (*User, *Resp } // List returns a list of users. -func (c *UsersService) List(ctx context.Context, params interface{}) ([]*User, *Response, error) { - var users []*User - resp, err := c.client.List(ctx, "users", params, &users) - return users, resp, err +func (c *UsersService) List(ctx context.Context, opts *UserListOptions) ([]*User, *Response, error) { + u, err := addOptions("users", opts) + if err != nil { + return nil, nil, err + } + + req, err := c.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + users := []*User{} + resp, err := c.client.Do(ctx, req, &users) + if err != nil { + return nil, resp, err + } + + return users, resp, nil } // Create creates a new user. @@ -69,10 +83,10 @@ func (c *UsersService) Get(ctx context.Context, id int, params interface{}) (*Us } // Update updates a single term with the given id. -func (c *UsersService) Update(ctx context.Context, id int, post *User) (*User, *Response, error) { +func (c *UsersService) Update(ctx context.Context, id int, user *User) (*User, *Response, error) { var updated User entityURL := fmt.Sprintf("users/%v", id) - resp, err := c.client.Update(ctx, entityURL, post, &updated) + resp, err := c.client.Update(ctx, entityURL, user, &updated) return &updated, resp, err } From 3acbc105dbedadb4134ba47c754433d5121b4312 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 02:55:28 -0800 Subject: [PATCH 38/46] Move common list fields to ListOptions --- client.go | 23 +++++++++-------------- list_options.go | 45 +++++++++------------------------------------ 2 files changed, 18 insertions(+), 50 deletions(-) diff --git a/client.go b/client.go index 870dd77..4ddb34c 100644 --- a/client.go +++ b/client.go @@ -91,20 +91,15 @@ type service struct { // ListOptions specifies the optional parameters to various List methods that // support pagination. type ListOptions struct { - // For paginated result sets, page of results to retrieve. - Page int `url:"page,omitempty"` - - // For paginated result sets, the number of results to include per page. - PerPage int `url:"per_page,omitempty"` - - // Offset the result set by a specific number of items. - Offset int `url:"offset,omitempty"` - // Order sort attribute ascending or descending. - Order string `url:"order,omitempty"` - // Sort collection by object attribute. - OrderBy string `url:"orderby,omitempty"` - // Scope under which the request is made; determines fields present in response. - Context string `url:"context,omitempty"` + Context string `url:"context,omitempty"` // Scope under which the request is made; determines fields present in response. + Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. + Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. + Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. + Order string `url:"order,omitempty"` // Order sort attribute ascending or descending. + OrderBy string `url:"orderby,omitempty"` // Sort collection by object attribute. + Page int `url:"page,omitempty"` // Current page of the collection. + PerPage int `url:"per_page,omitempty"` // Maximum number of items to be returned in result set. + Search string `url:"search,omitempty"` // Limit results to those matching a string. } // Response is a WordPress REST API response. This wraps the standard http.Response diff --git a/list_options.go b/list_options.go index 0c4d178..bf2ea9e 100644 --- a/list_options.go +++ b/list_options.go @@ -6,13 +6,10 @@ package wordpress // CategoryListOptions are options that can be passed to List(). type CategoryListOptions struct { - Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. - HideEmpty bool `url:"hide_empty,omitempty"` // Whether to hide terms not assigned to any posts. - Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. - Parent int `url:"parent,omitempty"` // Limit result set to terms assigned to a specific parent. - Post int `url:"post,omitempty"` // Limit result set to terms assigned to a specific post. - Search string `url:"search,omitempty"` // Limit results to those matching a string. - Slug []string `url:"slug,omitempty,brackets"` // Limit result set to terms with one or more specific slugs. + HideEmpty bool `url:"hide_empty,omitempty"` // Whether to hide terms not assigned to any posts. + Parent int `url:"parent,omitempty"` // Limit result set to terms assigned to a specific parent. + Post int `url:"post,omitempty"` // Limit result set to terms assigned to a specific post. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to terms with one or more specific slugs. ListOptions } @@ -24,14 +21,10 @@ type CommentListOptions struct { AuthorEmail string `url:"author_email,omitempty"` // Limit result set to that from a specific author email. Requires authorization. AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes comments assigned to specific user IDs. Requires authorization. Before string `url:"before,omitempty"` // Limit response to comments published before a given ISO8601 compliant date. - Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. - Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. - Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. Parent []int `url:"parent,omitempty,brackets"` // Limit result set to comments of specific parent IDs. ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Ensure result set excludes specific parent IDs. Password string `url:"password,omitempty"` // The password for the post if it is password protected. Post []int `url:"post,omitempty,brackets"` // Limit result set to comments assigned to specific post IDs. - Search string `url:"search,omitempty"` // Limit results to those matching a string. Status string `url:"status,omitempty"` // Limit result set to comments assigned a specific status. Requires authorization. Type string `url:"type,omitempty"` // Limit result set to comments assigned a specific type. Requires authorization. @@ -44,14 +37,10 @@ type MediaListOptions struct { Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. - Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. - Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. MediaType string `url:"media_type,omitempty"` // Limit result set to attachments of a particular media type. MimeType string `url:"mime_type,omitempty"` // Limit result set to attachments of a particular MIME type. - Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. Parent []int `url:"parent,omitempty,brackets"` // Limit result set to items with particular parent IDs. ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Limit result set to all items except those of a particular parent ID. - Search string `url:"search,omitempty"` // Limit results to those matching a string. Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. @@ -64,13 +53,9 @@ type PageListOptions struct { Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. - Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. - Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. MenuOrder int `url:"menu_order,omitempty"` // Limit result set to posts with a specific menu_order value. - Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. Parent []int `url:"parent,omitempty,brackets"` // Limit result set to items with particular parent IDs. ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Limit result set to all items except those of a particular parent ID. - Search string `url:"search,omitempty"` // Limit results to those matching a string. Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. @@ -85,10 +70,6 @@ type PostListOptions struct { Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. Categories []int `url:"categories,omitempty,brackets"` // Limit result set to all items that have the specified term assigned in the categories taxonomy. CategoriesExclude []int `url:"categories_exclude,omitempty,brackets"` // Limit result set to all items except those that have the specified term assigned in the categories taxonomy. - Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. - Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. - Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. - Search string `url:"search,omitempty"` // Limit results to those matching a string. Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. Sticky bool `url:"sticky,omitempty"` // Limit result set to items that are sticky. @@ -100,25 +81,17 @@ type PostListOptions struct { // TagListOptions are options that can be passed to List(). type TagListOptions struct { - Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. - HideEmpty bool `url:"hide_empty,omitempty"` // Whether to hide terms not assigned to any posts. - Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. - Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. - Post int `url:"post,omitempty"` // Limit result set to terms assigned to a specific post. - Search string `url:"search,omitempty"` // Limit results to those matching a string. - Slug []string `url:"slug,omitempty,brackets"` // Limit result set to terms with one or more specific slugs. + HideEmpty bool `url:"hide_empty,omitempty"` // Whether to hide terms not assigned to any posts. + Post int `url:"post,omitempty"` // Limit result set to terms assigned to a specific post. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to terms with one or more specific slugs. ListOptions } // UserListOptions are options that can be passed to List(). type UserListOptions struct { - Exclude []int `url:"exclude,omitempty,brackets"` // Ensure result set excludes specific IDs. - Include []int `url:"include,omitempty,brackets"` // Limit result set to specific IDs. - Offset int `url:"offset,omitempty"` // Offset the result set by a specific number of items. - Roles []string `url:"roles,omitempty,brackets"` // Limit result set to users matching at least one specific role provided. Accepts csv list or single role. - Search string `url:"search,omitempty"` // Limit results to those matching a string. - Slug []string `url:"slug,omitempty,brackets"` // Limit result set to users with one or more specific slugs. + Roles []string `url:"roles,omitempty,brackets"` // Limit result set to users matching at least one specific role provided. Accepts csv list or single role. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to users with one or more specific slugs. ListOptions } From 1968354161146b5503ff54ca25c414ef1d26becf Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 02:59:59 -0800 Subject: [PATCH 39/46] Fix tests --- pages_test.go | 2 +- posts_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pages_test.go b/pages_test.go index 4d8c370..a533dc5 100644 --- a/pages_test.go +++ b/pages_test.go @@ -86,7 +86,7 @@ func TestPagesList_WithParamsString(t *testing.T) { wp, ctx := initTestClient() // assumes that API user authenticated with `edit_pages` - pages, resp, err := wp.Pages.List(ctx, &wordpress.PageListOptions{Status: "draft"}) + pages, resp, err := wp.Pages.List(ctx, &wordpress.PageListOptions{Status: []string{"draft"}}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } diff --git a/posts_test.go b/posts_test.go index fb0ea57..e8f6a1b 100644 --- a/posts_test.go +++ b/posts_test.go @@ -87,7 +87,7 @@ func TestPostsList_WithParamsString(t *testing.T) { wp, ctx := initTestClient() // assumes that API user authenticated with `edit_posts` - posts, resp, err := wp.Posts.List(ctx, &wordpress.PostListOptions{Status: "draft"}) + posts, resp, err := wp.Posts.List(ctx, &wordpress.PostListOptions{Status: []string{"draft"}}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } @@ -98,7 +98,7 @@ func TestPostsList_WithParamsString(t *testing.T) { if len(posts) != 0 { t.Errorf("Should return zero draft posts, returned %v", len(posts)) } - posts, resp, err = wp.Posts.List(ctx, &wordpress.PostListOptions{Status: "publish"}) + posts, resp, err = wp.Posts.List(ctx, &wordpress.PostListOptions{Status: []string{"publish"}}) if err != nil { t.Errorf("Should not return error: %v", err.Error()) } From 01b6fbf906992e4aba20da68acc4ae2259426b59 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 03:08:03 -0800 Subject: [PATCH 40/46] minor README.md tweaks --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 391e8a4..cfac2ff 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# go-wp-api +# go-wordpress [![GoDoc](https://godoc.org/github.com/robbiet480/go-wordpress?status.svg)](https://godoc.org/github.com/robbiet480/go-wordpress) A Go client library for the [Wordpress REST API](https://developer.wordpress.org/rest-api/) @@ -43,7 +43,7 @@ func main() { } // Or you can use your own structs (for custom endpoints, for example) - // Below is the equivalent of `client.Posts.Get(100, nil)` + // Below is the equivalent of `client.Posts.Get(ctx, 100, nil)` var obj MyCustomPostStruct resp, err := client.Get(ctx, "/posts/100", nil, &obj) // ... From d2de05b06e20164c137987280fc1f0ca670b5357 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 03:08:43 -0800 Subject: [PATCH 41/46] Remove WordPressRESTAPI struct --- list_options.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/list_options.go b/list_options.go index bf2ea9e..6c0650a 100644 --- a/list_options.go +++ b/list_options.go @@ -95,8 +95,3 @@ type UserListOptions struct { ListOptions } - -// WordPressRESTAPI -type WordPressRESTAPI struct { - ListOptions -} From 9c30e55e2485029449abe94bff4819883daa5e6c Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 03:10:23 -0800 Subject: [PATCH 42/46] fix user comments --- users.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/users.go b/users.go index c2524a2..baa26e3 100644 --- a/users.go +++ b/users.go @@ -74,7 +74,7 @@ func (c *UsersService) Create(ctx context.Context, newUser *User) (*User, *Respo return &created, resp, err } -// Get returns a single term for the given id. +// Get returns a single user for the given id. func (c *UsersService) Get(ctx context.Context, id int, params interface{}) (*User, *Response, error) { var entity User entityURL := fmt.Sprintf("users/%v", id) @@ -82,7 +82,7 @@ func (c *UsersService) Get(ctx context.Context, id int, params interface{}) (*Us return &entity, resp, err } -// Update updates a single term with the given id. +// Update updates a single user with the given id. func (c *UsersService) Update(ctx context.Context, id int, user *User) (*User, *Response, error) { var updated User entityURL := fmt.Sprintf("users/%v", id) @@ -90,7 +90,7 @@ func (c *UsersService) Update(ctx context.Context, id int, user *User) (*User, * return &updated, resp, err } -// Delete removes the term with the given id. +// Delete removes the user with the given id. func (c *UsersService) Delete(ctx context.Context, id int, params interface{}) (*User, *Response, error) { var deleted User entityURL := fmt.Sprintf("users/%v", id) From 0ff8aa328b1a3e30ffa8199817c23e571a589e6e Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 03:15:42 -0800 Subject: [PATCH 43/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cfac2ff..8f1a26e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ package main import ( "context" - "log" + "fmt" "net/http" "github.com/robbiet480/go-wordpress" From 5d516a1c04cb6bb27576639636b0a5f63ba775f2 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Mon, 5 Feb 2018 15:19:55 -0800 Subject: [PATCH 44/46] Use time.Time for before/after --- list_options.go | 84 +++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/list_options.go b/list_options.go index 6c0650a..370f062 100644 --- a/list_options.go +++ b/list_options.go @@ -4,6 +4,8 @@ package wordpress +import "time" + // CategoryListOptions are options that can be passed to List(). type CategoryListOptions struct { HideEmpty bool `url:"hide_empty,omitempty"` // Whether to hide terms not assigned to any posts. @@ -16,65 +18,65 @@ type CategoryListOptions struct { // CommentListOptions are options that can be passed to List(). type CommentListOptions struct { - After string `url:"after,omitempty"` // Limit response to comments published after a given ISO8601 compliant date. - Author []int `url:"author,omitempty,brackets"` // Limit result set to comments assigned to specific user IDs. Requires authorization. - AuthorEmail string `url:"author_email,omitempty"` // Limit result set to that from a specific author email. Requires authorization. - AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes comments assigned to specific user IDs. Requires authorization. - Before string `url:"before,omitempty"` // Limit response to comments published before a given ISO8601 compliant date. - Parent []int `url:"parent,omitempty,brackets"` // Limit result set to comments of specific parent IDs. - ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Ensure result set excludes specific parent IDs. - Password string `url:"password,omitempty"` // The password for the post if it is password protected. - Post []int `url:"post,omitempty,brackets"` // Limit result set to comments assigned to specific post IDs. - Status string `url:"status,omitempty"` // Limit result set to comments assigned a specific status. Requires authorization. - Type string `url:"type,omitempty"` // Limit result set to comments assigned a specific type. Requires authorization. + After *time.Time `url:"after,omitempty"` // Limit response to comments published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to comments assigned to specific user IDs. Requires authorization. + AuthorEmail string `url:"author_email,omitempty"` // Limit result set to that from a specific author email. Requires authorization. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes comments assigned to specific user IDs. Requires authorization. + Before *time.Time `url:"before,omitempty"` // Limit response to comments published before a given ISO8601 compliant date. + Parent []int `url:"parent,omitempty,brackets"` // Limit result set to comments of specific parent IDs. + ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Ensure result set excludes specific parent IDs. + Password string `url:"password,omitempty"` // The password for the post if it is password protected. + Post []int `url:"post,omitempty,brackets"` // Limit result set to comments assigned to specific post IDs. + Status string `url:"status,omitempty"` // Limit result set to comments assigned a specific status. Requires authorization. + Type string `url:"type,omitempty"` // Limit result set to comments assigned a specific type. Requires authorization. ListOptions } // MediaListOptions are options that can be passed to List(). type MediaListOptions struct { - After string `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. - Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. - AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. - Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. - MediaType string `url:"media_type,omitempty"` // Limit result set to attachments of a particular media type. - MimeType string `url:"mime_type,omitempty"` // Limit result set to attachments of a particular MIME type. - Parent []int `url:"parent,omitempty,brackets"` // Limit result set to items with particular parent IDs. - ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Limit result set to all items except those of a particular parent ID. - Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. - Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. + After *time.Time `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. + Before *time.Time `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. + MediaType string `url:"media_type,omitempty"` // Limit result set to attachments of a particular media type. + MimeType string `url:"mime_type,omitempty"` // Limit result set to attachments of a particular MIME type. + Parent []int `url:"parent,omitempty,brackets"` // Limit result set to items with particular parent IDs. + ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Limit result set to all items except those of a particular parent ID. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. + Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. ListOptions } // PageListOptions are options that can be passed to List(). type PageListOptions struct { - After string `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. - Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. - AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. - Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. - MenuOrder int `url:"menu_order,omitempty"` // Limit result set to posts with a specific menu_order value. - Parent []int `url:"parent,omitempty,brackets"` // Limit result set to items with particular parent IDs. - ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Limit result set to all items except those of a particular parent ID. - Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. - Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. + After *time.Time `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. + Before *time.Time `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. + MenuOrder int `url:"menu_order,omitempty"` // Limit result set to posts with a specific menu_order value. + Parent []int `url:"parent,omitempty,brackets"` // Limit result set to items with particular parent IDs. + ParentExclude []int `url:"parent_exclude,omitempty,brackets"` // Limit result set to all items except those of a particular parent ID. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. + Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. ListOptions } // PostListOptions are options that can be passed to List(). type PostListOptions struct { - After string `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. - Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. - AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. - Before string `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. - Categories []int `url:"categories,omitempty,brackets"` // Limit result set to all items that have the specified term assigned in the categories taxonomy. - CategoriesExclude []int `url:"categories_exclude,omitempty,brackets"` // Limit result set to all items except those that have the specified term assigned in the categories taxonomy. - Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. - Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. - Sticky bool `url:"sticky,omitempty"` // Limit result set to items that are sticky. - Tags []int `url:"tags,omitempty,brackets"` // Limit result set to all items that have the specified term assigned in the tags taxonomy. - TagsExclude []int `url:"tags_exclude,omitempty,brackets"` // Limit result set to all items except those that have the specified term assigned in the tags taxonomy. + After *time.Time `url:"after,omitempty"` // Limit response to posts published after a given ISO8601 compliant date. + Author []int `url:"author,omitempty,brackets"` // Limit result set to posts assigned to specific authors. + AuthorExclude []int `url:"author_exclude,omitempty,brackets"` // Ensure result set excludes posts assigned to specific authors. + Before *time.Time `url:"before,omitempty"` // Limit response to posts published before a given ISO8601 compliant date. + Categories []int `url:"categories,omitempty,brackets"` // Limit result set to all items that have the specified term assigned in the categories taxonomy. + CategoriesExclude []int `url:"categories_exclude,omitempty,brackets"` // Limit result set to all items except those that have the specified term assigned in the categories taxonomy. + Slug []string `url:"slug,omitempty,brackets"` // Limit result set to posts with one or more specific slugs. + Status []string `url:"status,omitempty,brackets"` // Limit result set to posts assigned one or more statuses. + Sticky bool `url:"sticky,omitempty"` // Limit result set to items that are sticky. + Tags []int `url:"tags,omitempty,brackets"` // Limit result set to all items that have the specified term assigned in the tags taxonomy. + TagsExclude []int `url:"tags_exclude,omitempty,brackets"` // Limit result set to all items except those that have the specified term assigned in the tags taxonomy. ListOptions } From 1726f02e50d21e1b3fa4f113f046ad63db0f641f Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Tue, 6 Feb 2018 11:40:07 -0800 Subject: [PATCH 45/46] Fix BasicInfo request --- client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 4ddb34c..416e363 100644 --- a/client.go +++ b/client.go @@ -342,7 +342,8 @@ type RootInfo struct { // BasicInfo gets basic and publicly available information about the WordPress REST API. func (c *Client) BasicInfo(ctx context.Context) (*RootInfo, *Response, error) { var entity RootInfo - resp, err := c.Get(ctx, c.baseURL.String(), nil, &entity) + + resp, err := c.Get(ctx, "", nil, &entity) if err != nil { return &entity, resp, err } From 3b8369ffcef39c339f0636c3b12bec8fea0701b7 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Tue, 6 Feb 2018 12:15:00 -0800 Subject: [PATCH 46/46] Authentication can be an array instead of map --- client.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client.go b/client.go index 416e363..9fcc0d8 100644 --- a/client.go +++ b/client.go @@ -326,15 +326,15 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res // RootInfo is a struct containing basic and publicly available information about the WordPress REST API. type RootInfo struct { - Authentication map[string]interface{} `json:"authentication"` - Description string `json:"description"` - GMTOffset int `json:"gmt_offset"` - HomeURL string `json:"home"` - Name string `json:"name"` - Namespaces []string `json:"namespaces"` - PermalinkStructure string `json:"permalink_structure"` - TimezoneString string `json:"timezone_string"` - URL string `json:"url"` + Authentication interface{} `json:"authentication"` + Description string `json:"description"` + GMTOffset int `json:"gmt_offset"` + HomeURL string `json:"home"` + Name string `json:"name"` + Namespaces []string `json:"namespaces"` + PermalinkStructure string `json:"permalink_structure"` + TimezoneString string `json:"timezone_string"` + URL string `json:"url"` Location *time.Location `json:"-"` }