Skip to content

Commit d669868

Browse files
committed
Use GraphQL to retrieve products and variants
REST endpoint is deprecated. New method is A LOT slower (~4x)
1 parent 6dcc0e9 commit d669868

6 files changed

Lines changed: 143 additions & 35 deletions

File tree

exportformat/csv.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import (
44
"encoding/csv"
55
"fmt"
66
"os"
7-
"strconv"
87

9-
shopify "github.com/bold-commerce/go-shopify/v3"
8+
"github.com/screenstaring/shopify_id_export/gql"
109
)
1110

1211
type CSV struct {
@@ -40,20 +39,21 @@ func NewCSV(shop string) (*CSV, error) {
4039
return c, nil
4140
}
4241

43-
func (c *CSV) Dump(product shopify.Product) error {
42+
func (c *CSV) Dump(product gql.Product) error {
4443
var err error
4544

4645
if !c.headerWritten {
4746
c.out.Write(c.header)
4847
c.headerWritten = true
4948
}
5049

51-
for _, variant := range product.Variants {
50+
for _, edge := range product.Variants.Edges {
51+
variant := edge.Node
5252
row := []string{
53-
strconv.FormatInt(variant.ProductID, 10),
53+
product.ID,
5454
product.Title,
5555
product.ProductType,
56-
strconv.FormatInt(variant.ID, 10),
56+
variant.ID,
5757
variant.Title,
5858
variant.Sku,
5959
variant.Barcode,

exportformat/json.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import (
44
"encoding/json"
55
"fmt"
66
"os"
7-
"strconv"
87

9-
shopify "github.com/bold-commerce/go-shopify/v3"
8+
"github.com/screenstaring/shopify_id_export/gql"
109
)
1110

1211
type JSON struct {
1312
out *os.File
1413
root string
15-
products []shopify.Product
14+
products []gql.Product
1615
}
1716

1817
var JSONRootProperties = []string{
@@ -34,22 +33,22 @@ func isValidJSONRootProperty(name string) bool {
3433
return false
3534
}
3635

37-
func productMap(product shopify.Product) map[string]interface{} {
36+
func productMap(product gql.Product) map[string]interface{} {
3837
record := make(map[string]interface{})
3938

40-
record["product_id"] = strconv.FormatInt(product.ID, 10)
39+
record["product_id"] = product.ID
4140
record["handle"] = product.Handle
4241
record["product_title"] = product.Title
4342
record["product_type"] = product.ProductType
4443

4544
return record
4645
}
4746

48-
func variantMap(variant shopify.Variant) map[string]string {
47+
func variantMap(variant gql.Variant) map[string]string {
4948
record := make(map[string]string)
5049

5150
record["barcode"] = variant.Barcode
52-
record["variant_id"] = strconv.FormatInt(variant.ID, 10)
51+
record["variant_id"] = variant.ID
5352
record["variant_title"] = variant.Title
5453
record["sku"] = variant.Sku
5554

@@ -68,7 +67,8 @@ func (j *JSON) formatWithVariantRoot() map[string]interface{} {
6867
output := make(map[string]interface{})
6968

7069
for _, product := range j.products {
71-
for _, variant := range product.Variants {
70+
for _, vEdge := range product.Variants.Edges {
71+
variant := vEdge.Node
7272
record := variantMap(variant)
7373
key := record[j.root]
7474
if len(key) == 0 {
@@ -103,7 +103,8 @@ func (j *JSON) formatWithProduct() interface{} {
103103
record := productMap(product)
104104

105105
var variants []map[string]string
106-
for _, variant := range product.Variants {
106+
for _, vEdge := range product.Variants.Edges {
107+
variant := vEdge.Node
107108
variants = append(variants, variantMap(variant))
108109
}
109110

@@ -121,7 +122,8 @@ func (j *JSON) formatWithProductRoot() map[string]interface{} {
121122
record := productMap(product)
122123

123124
var variants []map[string]string
124-
for _, variant := range product.Variants {
125+
for _, vEdge := range product.Variants.Edges {
126+
variant := vEdge.Node
125127
variants = append(variants, variantMap(variant))
126128
}
127129

@@ -156,7 +158,7 @@ func NewJSON(shop string, jsonRoot string) (*JSON, error) {
156158
return j, nil
157159
}
158160

159-
func (j *JSON) Dump(product shopify.Product) error {
161+
func (j *JSON) Dump(product gql.Product) error {
160162
j.products = append(j.products, product)
161163

162164
return nil

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ module github.com/screenstaring/shopify_id_export
22

33
go 1.16
44

5-
require github.com/bold-commerce/go-shopify/v3 v3.17.0 // indirect
5+
require (
6+
github.com/bold-commerce/go-shopify/v3 v3.17.0 // indirect
7+
github.com/bold-commerce/go-shopify/v4 v4.6.0 // indirect
8+
github.com/google/go-querystring v1.1.0 // indirect
9+
github.com/shopspring/decimal v1.4.0 // indirect
10+
)

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
github.com/bold-commerce/go-shopify/v3 v3.17.0 h1:1qZenleSsJMVFh5hu6R2z2NfmWP5xG0P8MawePr46K0=
22
github.com/bold-commerce/go-shopify/v3 v3.17.0/go.mod h1:qOrEfYoy5RRO/PAq4vGyHW03NZmt2iX/fPGuaZwemtI=
3+
github.com/bold-commerce/go-shopify/v4 v4.6.0 h1:TqCNix3Qk2pwMDkrN1ZXJ91c3TMiIbtIkAPyx94EUZA=
4+
github.com/bold-commerce/go-shopify/v4 v4.6.0/go.mod h1:Sjg+C2CLNhYeCbwB6EedKgj2AHEBOP3ng+Eu1IfMf1U=
35
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
47
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
58
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
9+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
10+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
611
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
712
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
813
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114 h1:Pm6R878vxWWWR+Sa3ppsLce/Zq+JNTs6aVvRu13jv9A=
914
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
15+
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
16+
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
17+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

gql/client.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package gql
2+
3+
import (
4+
"context"
5+
shopify "github.com/bold-commerce/go-shopify/v4"
6+
)
7+
8+
const productsQuery = `
9+
query findProducts($after: String) {
10+
products(first:250 after:$after) {
11+
pageInfo {
12+
hasNextPage
13+
endCursor
14+
}
15+
edges {
16+
node {
17+
legacyResourceId
18+
handle
19+
productType
20+
title
21+
variants(first:100) {
22+
edges {
23+
node {
24+
barcode
25+
legacyResourceId
26+
sku
27+
title
28+
}
29+
}
30+
}
31+
}
32+
}
33+
}
34+
}
35+
`
36+
37+
type productResponse struct {
38+
Products Products `json:"products"`
39+
}
40+
41+
type Products struct {
42+
Edges []ProductEdge `json:"edges"`
43+
PageInfo PageInfo `json:"pageInfo"`
44+
}
45+
46+
type ProductEdge struct {
47+
Node Product `json:"node"`
48+
}
49+
50+
type PageInfo struct {
51+
HasNextPage bool `json:"hasNextPage"`
52+
Cursor string `json:"endCursor"`
53+
}
54+
55+
type Product struct {
56+
Handle string `json:"handle"`
57+
ID string `json:"legacyResourceId"`
58+
Title string `json:"title"`
59+
ProductType string `json:"productType"`
60+
Variants Variants `json:"variants"`
61+
}
62+
63+
type Variants struct {
64+
Edges []VariantEdge `json:"edges"`
65+
}
66+
67+
type VariantEdge struct {
68+
Node Variant `json:"node"`
69+
}
70+
71+
type Variant struct {
72+
Barcode string `json:"barcode"`
73+
ID string `json:"legacyResourceId"`
74+
ProductID string `json:"productId"`
75+
Sku string `json:"sku"`
76+
Title string `json:"title"`
77+
}
78+
79+
func FindProducts(client *shopify.Client, options map[string]interface{}) (Products, error) {
80+
response := productResponse{}
81+
82+
err := client.GraphQL.Query(context.Background(), productsQuery, options, &response)
83+
84+
return response.Products, err
85+
}

main.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import (
88
"strings"
99
"time"
1010

11-
shopify "github.com/bold-commerce/go-shopify/v3"
11+
shopify "github.com/bold-commerce/go-shopify/v4"
1212
"github.com/screenstaring/shopify_id_export/exportformat"
13+
"github.com/screenstaring/shopify_id_export/gql"
1314
)
1415

15-
const version = "v0.0.7"
16+
const version = "v0.1.0"
1617
const shopifyFields = "id,title,product_type,handle,variants"
1718

1819
const usage = `shopify_id_export [hjv] [-k key] [-p password] [-r root-property] [-t token] shop
@@ -35,7 +36,7 @@ Valid properties for the --json-root option are: %s
3536
`
3637

3738
type dumper interface {
38-
Dump(shopify.Product) error
39+
Dump(gql.Product) error
3940
Close() error
4041
}
4142

@@ -45,34 +46,32 @@ func exitFailure(error string, code int) {
4546
}
4647

4748
func dumpProducts(client *shopify.Client, dumper dumper, pageSize int) error {
48-
listOptions := shopify.ListOptions{
49-
Fields: shopifyFields,
50-
Limit: pageSize,
49+
listOptions := map[string]interface{}{
50+
"after": nil,
5151
}
5252

5353
for {
54-
products, pages, err := client.Product.ListWithPagination(
55-
shopify.ProductListOptions{
56-
ListOptions: listOptions,
57-
},
58-
)
59-
54+
products, err := gql.FindProducts(client, listOptions)
6055
if err != nil {
6156
return fmt.Errorf("Failed retrieve products: "+err.Error())
6257
}
6358

64-
for _, product := range products {
59+
page := products.PageInfo
60+
61+
for _, edge := range(products.Edges) {
62+
product := edge.Node
63+
6564
err = dumper.Dump(product)
6665
if err != nil {
6766
return fmt.Errorf("Failed saving products: "+err.Error())
6867
}
6968
}
7069

71-
if pages.NextPageOptions == nil {
70+
if !page.HasNextPage {
7271
break
7372
}
7473

75-
listOptions.PageInfo = pages.NextPageOptions.PageInfo
74+
listOptions["after"] = page.Cursor
7675
}
7776

7877
return nil
@@ -86,7 +85,12 @@ func main() {
8685
var pageSize int
8786
var client *shopify.Client
8887

89-
options := []shopify.Option{shopify.WithRetry(5)}
88+
options := []shopify.Option{
89+
shopify.WithRetry(5),
90+
// Bug forces us to specify version
91+
// https://github.com/bold-commerce/go-shopify/issues/314
92+
shopify.WithVersion("2024-10"),
93+
}
9094

9195
flag.Usage = func() {
9296
exitFailure(fmt.Sprintf(usage, strings.Join(exportformat.JSONRootProperties, ", ")), 2)
@@ -141,6 +145,7 @@ func main() {
141145
if timeout > -1 {
142146
options = append(
143147
options,
148+
// FIXME: update for GQL
144149
shopify.WithHTTPClient(
145150
&http.Client{
146151
Timeout: time.Second * time.Duration(timeout),
@@ -153,7 +158,10 @@ func main() {
153158
options = append(options, shopify.WithLogger(&shopify.LeveledLogger{Level: shopify.LevelDebug}))
154159
}
155160

156-
client = shopify.NewClient(app, argv[0], token, options...)
161+
client, err = shopify.NewClient(app, argv[0], token, options...)
162+
if err != nil {
163+
exitFailure(err.Error(), 2)
164+
}
157165

158166
err = dumpProducts(client, dumper, pageSize)
159167
dumpErr := dumper.Close()

0 commit comments

Comments
 (0)