forked from getconversio/go-shopify
-
Notifications
You must be signed in to change notification settings - Fork 263
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ArticlesService in the client. (#309)
Implement ArticlesService for managing Shopify articles, including CRUD operations, tag related operations, and article count. Co-authored-by: Oliver <[email protected]>
- Loading branch information
Showing
3 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package goshopify | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
) | ||
|
||
const articlesBasePath = "articles" | ||
|
||
// The ArticlesService allows you to create, publish, and edit articles on a shop's blog | ||
// See: https://shopify.dev/docs/api/admin-rest/stable/resources/article | ||
type ArticlesService interface { | ||
List(context.Context, uint64, interface{}) ([]Article, error) | ||
Create(context.Context, uint64, Article) (*Article, error) | ||
Get(context.Context, uint64, uint64) (*Article, error) | ||
Update(context.Context, uint64, uint64, Article) (*Article, error) | ||
Delete(context.Context, uint64, uint64) error | ||
Count(context.Context, uint64, interface{}) (int, error) | ||
ListTags(context.Context, interface{}) ([]string, error) | ||
ListBlogTags(context.Context, uint64, interface{}) ([]string, error) | ||
} | ||
|
||
type ArticleResource struct { | ||
Article *Article `json:"article"` | ||
} | ||
|
||
type ArticlesResource struct { | ||
Articles []Article `json:"articles"` | ||
} | ||
|
||
// ArticlesServiceOp handles communication with the articles related methods of | ||
// the Shopify API. | ||
type ArticlesServiceOp struct { | ||
client *Client | ||
} | ||
|
||
type ArticleTagsResource struct { | ||
Tags []string `json:"tags,omitempty"` | ||
} | ||
|
||
type ArticleImage struct { | ||
CreatedAt *time.Time `json:"created_at,omitempty"` | ||
Alt string `json:"alt,omitempty"` | ||
Width int `json:"width,omitempty"` | ||
Height int `json:"height,omitempty"` | ||
Src string `json:"src,omitempty"` | ||
} | ||
|
||
type MetaFields struct { | ||
Key string `json:"key,omitempty"` | ||
Value string `json:"value,omitempty"` | ||
Type string `json:"type,omitempty"` | ||
Namespace string `json:"namespace,omitempty"` | ||
} | ||
|
||
type Article struct { | ||
Author string `json:"author,omitempty"` | ||
BlogId uint64 `json:"blog_id,omitempty"` | ||
BodyHtml string `json:"body_html,omitempty"` | ||
Id uint64 `json:"id,omitempty"` | ||
Handle string `json:"handle,omitempty"` | ||
Image *ArticleImage `json:"image,omitempty"` | ||
Metafields *MetaFields `json:"metafields"` | ||
Published bool `json:"published,omitempty"` | ||
SummaryHtml string `json:"summary_html,omitempty"` | ||
Tags string `json:"tags,omitempty"` | ||
Title string `json:"title,omitempty"` | ||
UpdatedAt time.Time `json:"updated_at,omitempty"` | ||
UserId int `json:"user_id,omitempty"` | ||
PublishedAt *time.Time `json:"published_at,omitempty"` | ||
CreatedAt *time.Time `json:"created_at,omitempty"` | ||
} | ||
|
||
// List all the articles in a blog. | ||
func (s *ArticlesServiceOp) List(ctx context.Context, blogId uint64, options interface{}) ([]Article, error) { | ||
path := fmt.Sprintf("%s/%d/%s.json", blogsBasePath, blogId, articlesBasePath) | ||
resource := new(ArticlesResource) | ||
err := s.client.Get(ctx, path, resource, options) | ||
return resource.Articles, err | ||
} | ||
|
||
// Create a article in a blog. | ||
func (s *ArticlesServiceOp) Create(ctx context.Context, blogId uint64, article Article) (*Article, error) { | ||
path := fmt.Sprintf("%s/%d/%s.json", blogsBasePath, blogId, articlesBasePath) | ||
body := ArticleResource{ | ||
Article: &article, | ||
} | ||
resource := new(ArticleResource) | ||
err := s.client.Post(ctx, path, body, resource) | ||
return resource.Article, err | ||
} | ||
|
||
// Get an article by blog id and article id. | ||
func (s *ArticlesServiceOp) Get(ctx context.Context, blogId uint64, articleId uint64) (*Article, error) { | ||
path := fmt.Sprintf("%s/%d/%s/%d.json", blogsBasePath, blogId, articlesBasePath, articleId) | ||
resource := new(ArticleResource) | ||
err := s.client.Get(ctx, path, resource, nil) | ||
return resource.Article, err | ||
} | ||
|
||
// Update an article in a blog. | ||
func (s *ArticlesServiceOp) Update(ctx context.Context, blogId uint64, articleId uint64, article Article) (*Article, error) { | ||
path := fmt.Sprintf("%s/%d/%s/%d.json", blogsBasePath, blogId, articlesBasePath, articleId) | ||
wrappedData := ArticleResource{Article: &article} | ||
resource := new(ArticleResource) | ||
err := s.client.Put(ctx, path, wrappedData, resource) | ||
return resource.Article, err | ||
} | ||
|
||
// Delete an article in a blog. | ||
func (s *ArticlesServiceOp) Delete(ctx context.Context, blogId uint64, articleId uint64) error { | ||
path := fmt.Sprintf("%s/%d/%s/%d.json", blogsBasePath, blogId, articlesBasePath, articleId) | ||
return s.client.Delete(ctx, path) | ||
} | ||
|
||
// ListTags Get all tags from all articles. | ||
func (s *ArticlesServiceOp) ListTags(ctx context.Context, options interface{}) ([]string, error) { | ||
path := fmt.Sprintf("%s/tags.json", articlesBasePath) | ||
articleTags := new(ArticleTagsResource) | ||
err := s.client.Get(ctx, path, &articleTags, options) | ||
return articleTags.Tags, err | ||
} | ||
|
||
// Count Articles from a Blog. | ||
func (s *ArticlesServiceOp) Count(ctx context.Context, blogId uint64, options interface{}) (int, error) { | ||
path := fmt.Sprintf("%s/%d/%s/count.json", blogsBasePath, blogId, articlesBasePath) | ||
return s.client.Count(ctx, path, options) | ||
} | ||
|
||
// ListBlogTags Get all tags from all articles in a blog. | ||
func (s *ArticlesServiceOp) ListBlogTags(ctx context.Context, blogId uint64, options interface{}) ([]string, error) { | ||
path := fmt.Sprintf("%s/%d/%s/tags.json", blogsBasePath, blogId, articlesBasePath) | ||
articleTags := new(ArticleTagsResource) | ||
err := s.client.Get(ctx, path, &articleTags, options) | ||
return articleTags.Tags, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
package goshopify | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/jarcoal/httpmock" | ||
) | ||
|
||
func TestArticleList(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder( | ||
"GET", | ||
fmt.Sprintf("https://fooshop.myshopify.com/%s/blogs/241253187/articles.json", client.pathPrefix), | ||
httpmock.NewStringResponder( | ||
200, | ||
`{"articles": [{"id":1},{"id":2}]}`, | ||
), | ||
) | ||
|
||
articles, err := client.Article.List(context.Background(), 241253187, nil) | ||
if err != nil { | ||
t.Errorf("Article.List returned error: %v", err) | ||
} | ||
|
||
expected := []Article{ | ||
{ | ||
Id: 1, | ||
}, | ||
{ | ||
Id: 2, | ||
}, | ||
} | ||
if !reflect.DeepEqual(articles, expected) { | ||
t.Errorf("Articles.List returned %+v, expected %+v", articles, expected) | ||
} | ||
} | ||
|
||
func TestArticleCreate(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder( | ||
"POST", | ||
fmt.Sprintf("https://fooshop.myshopify.com/%s/blogs/241253187/articles.json", client.pathPrefix), | ||
httpmock.NewStringResponder( | ||
201, | ||
`{"article": {"id": 1}}`, | ||
), | ||
) | ||
|
||
article := Article{Title: "Test Article"} | ||
createdArticle, err := client.Article.Create(context.Background(), 241253187, article) | ||
if err != nil { | ||
t.Errorf("Article.Create returned error: %v", err) | ||
} | ||
|
||
expected := &Article{Id: 1} | ||
if !reflect.DeepEqual(createdArticle, expected) { | ||
t.Errorf("Article.Create returned %+v, expected %+v", createdArticle, expected) | ||
} | ||
} | ||
|
||
func TestArticleGet(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder( | ||
"GET", | ||
fmt.Sprintf("https://fooshop.myshopify.com/%s/blogs/241253187/articles/1.json", client.pathPrefix), | ||
httpmock.NewStringResponder( | ||
200, | ||
`{"article": {"id": 1, "title": "Test Article"}}`, | ||
), | ||
) | ||
|
||
article, err := client.Article.Get(context.Background(), 241253187, 1) | ||
if err != nil { | ||
t.Errorf("Article.Get returned error: %v", err) | ||
} | ||
|
||
expected := &Article{Id: 1, Title: "Test Article"} | ||
if !reflect.DeepEqual(article, expected) { | ||
t.Errorf("Article.Get returned %+v, expected %+v", article, expected) | ||
} | ||
} | ||
|
||
func TestArticleUpdate(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder( | ||
"PUT", | ||
fmt.Sprintf("https://fooshop.myshopify.com/%s/blogs/241253187/articles/1.json", client.pathPrefix), | ||
httpmock.NewStringResponder( | ||
200, | ||
`{"article": {"id": 1, "title": "Updated Article"}}`, | ||
), | ||
) | ||
|
||
article := Article{Title: "Updated Article"} | ||
updatedArticle, err := client.Article.Update(context.Background(), 241253187, 1, article) | ||
if err != nil { | ||
t.Errorf("Article.Update returned error: %v", err) | ||
} | ||
|
||
expected := &Article{Id: 1, Title: "Updated Article"} | ||
if !reflect.DeepEqual(updatedArticle, expected) { | ||
t.Errorf("Article.Update returned %+v, expected %+v", updatedArticle, expected) | ||
} | ||
} | ||
|
||
func TestArticleDelete(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder( | ||
"DELETE", | ||
fmt.Sprintf("https://fooshop.myshopify.com/%s/blogs/241253187/articles/1.json", client.pathPrefix), | ||
httpmock.NewStringResponder( | ||
204, // No content response | ||
``, | ||
), | ||
) | ||
|
||
err := client.Article.Delete(context.Background(), 241253187, 1) | ||
if err != nil { | ||
t.Errorf("Article.Delete returned error: %v", err) | ||
} | ||
} | ||
|
||
func TestArticleListTags(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder( | ||
"GET", | ||
fmt.Sprintf("https://fooshop.myshopify.com/%s/articles/tags.json", client.pathPrefix), | ||
httpmock.NewStringResponder( | ||
200, | ||
`{"tags": ["tag1", "tag2"]}`, | ||
), | ||
) | ||
|
||
tags, err := client.Article.ListTags(context.Background(), nil) | ||
if err != nil { | ||
t.Errorf("Article.ListTags returned error: %v", err) | ||
} | ||
|
||
expected := []string{"tag1", "tag2"} | ||
if !reflect.DeepEqual(tags, expected) { | ||
t.Errorf("Article.ListTags returned %+v, expected %+v", tags, expected) | ||
} | ||
} | ||
|
||
func TestArticleCount(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder( | ||
"GET", | ||
fmt.Sprintf("https://fooshop.myshopify.com/%s/blogs/241253187/articles/count.json", client.pathPrefix), | ||
httpmock.NewStringResponder( | ||
200, | ||
`{"count": 2}`, | ||
), | ||
) | ||
|
||
count, err := client.Article.Count(context.Background(), 241253187, nil) | ||
if err != nil { | ||
t.Errorf("Article.Count returned error: %v", err) | ||
} | ||
|
||
expected := 2 | ||
if count != expected { | ||
t.Errorf("Article.Count returned %d, expected %d", count, expected) | ||
} | ||
} | ||
|
||
func TestArticleListBlogTags(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder( | ||
"GET", | ||
fmt.Sprintf("https://fooshop.myshopify.com/%s/blogs/241253187/articles/tags.json", client.pathPrefix), | ||
httpmock.NewStringResponder( | ||
200, | ||
`{"tags": ["blogTag1", "blogTag2"]}`, | ||
), | ||
) | ||
|
||
tags, err := client.Article.ListBlogTags(context.Background(), 241253187, nil) | ||
if err != nil { | ||
t.Errorf("Article.ListBlogTags returned error: %v", err) | ||
} | ||
|
||
expected := []string{"blogTag1", "blogTag2"} | ||
if !reflect.DeepEqual(tags, expected) { | ||
t.Errorf("Article.ListBlogTags returned %+v, expected %+v", tags, expected) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters