@sommpicks/sommpicks-shopify 24.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Logger.ts +18 -0
- package/README.md +258 -0
- package/addTypings.sh +2 -0
- package/bitbucket-pipelines.yml +38 -0
- package/index.ts +132 -0
- package/package.json +57 -0
- package/publish.sh +20 -0
- package/services/CacheWrapper.ts +30 -0
- package/services/CountryCodeService.ts +507 -0
- package/shopify/ShopifyAppService.ts +109 -0
- package/shopify/ShopifyAssetService.ts +20 -0
- package/shopify/ShopifyBillingService.ts +73 -0
- package/shopify/ShopifyCartTrasnformationService.ts +207 -0
- package/shopify/ShopifyCollectionService.ts +523 -0
- package/shopify/ShopifyCustomerService.ts +472 -0
- package/shopify/ShopifyDeliveryCustomisationService.ts +220 -0
- package/shopify/ShopifyDiscountService.ts +131 -0
- package/shopify/ShopifyDraftOrderService.ts +125 -0
- package/shopify/ShopifyFulfillmentService.ts +41 -0
- package/shopify/ShopifyFunctionsProductDiscountsService.ts +166 -0
- package/shopify/ShopifyInventoryService.ts +415 -0
- package/shopify/ShopifyLocationService.ts +29 -0
- package/shopify/ShopifyOrderRefundsService.ts +138 -0
- package/shopify/ShopifyOrderRiskService.ts +19 -0
- package/shopify/ShopifyOrderService.ts +1143 -0
- package/shopify/ShopifyPageService.ts +62 -0
- package/shopify/ShopifyProductService.ts +772 -0
- package/shopify/ShopifyShippingZonesService.ts +37 -0
- package/shopify/ShopifyShopService.ts +101 -0
- package/shopify/ShopifyTemplateService.ts +30 -0
- package/shopify/ShopifyThemeService.ts +33 -0
- package/shopify/ShopifyUtils.ts +56 -0
- package/shopify/ShopifyWebhookService.ts +110 -0
- package/shopify/base/APIVersion.ts +4 -0
- package/shopify/base/AbstractService.ts +152 -0
- package/shopify/base/ErrorHelper.ts +24 -0
- package/shopify/errors/InspiraShopifyCustomError.ts +7 -0
- package/shopify/errors/InspiraShopifyError.ts +15 -0
- package/shopify/errors/InspiraShopifyUnableToReserveInventoryError.ts +7 -0
- package/shopify/helpers/ShopifyProductServiceHelper.ts +450 -0
- package/shopify/product/ShopifyProductCountService.ts +110 -0
- package/shopify/product/ShopifyProductListService.ts +333 -0
- package/shopify/product/ShopifyProductMetafieldsService.ts +405 -0
- package/shopify/product/ShopifyProductPublicationsService.ts +112 -0
- package/shopify/product/ShopifyVariantService.ts +584 -0
- package/shopify/router/ShopifyMandatoryRouter.ts +37 -0
- package/shopify/router/ShopifyRouter.ts +85 -0
- package/shopify/router/ShopifyRouterBis.ts +85 -0
- package/shopify/router/ShopifyRouterBisBis.ts +85 -0
- package/shopify/router/ShopifyRouterBisBisBis.ts +85 -0
- package/shopify/router/ShopifyRouterBisBisBisBis.ts +85 -0
- package/shopify/router/WebhookSkipMiddleware.ts +73 -0
- package/shopify/router/services/CryptoService.ts +26 -0
- package/shopify/router/services/HmacValidator.ts +36 -0
- package/shopify/router/services/OauthService.ts +17 -0
- package/shopify/router/services/RestUtils.ts +13 -0
- package/shopify/router/services/rateLimiter/MemoryStores.ts +46 -0
- package/shopify/router/services/rateLimiter/StoreRateLimiter.ts +46 -0
- package/test/README.md +223 -0
- package/test/router/ShopifyRouter.test.ts +71 -0
- package/test/router/WebhookSkipMiddleware.test.ts +86 -0
- package/test/router/services/HmacValidator.test.ts +24 -0
- package/test/router/services/RestUtils.test.ts +13 -0
- package/test/router/services/rateLimiter/StoreRateLimiter.test.ts +62 -0
- package/test/services/CacheWrapper.test.ts +30 -0
- package/test/shopify/ShopifyOrderService.test.ts +29 -0
- package/test/shopify/ShopifyProductService.test.ts +118 -0
- package/test/shopify/ShopifyWebhookService.test.ts +105 -0
- package/tsconfig.json +10 -0
- package/typings/axios.d.ts +8 -0
- package/typings/index.d.ts +1682 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import { Logger } from '../Logger';
|
|
2
|
+
import * as TimerQueue from 'timer-queue';
|
|
3
|
+
import { print } from 'graphql';
|
|
4
|
+
import gql from 'graphql-tag';
|
|
5
|
+
import { AxiosInstance } from 'axios';
|
|
6
|
+
import ErrorHelper from './base/ErrorHelper';
|
|
7
|
+
import { ShopifyProductService } from './ShopifyProductService';
|
|
8
|
+
import InspiraShopifyError from './errors/InspiraShopifyError';
|
|
9
|
+
import { AbstractService } from './base/AbstractService';
|
|
10
|
+
import * as parseLink from 'parse-link-header';
|
|
11
|
+
import moment = require('moment');
|
|
12
|
+
|
|
13
|
+
export class ShopifyCollectionService extends AbstractService {
|
|
14
|
+
|
|
15
|
+
private ShopifyProductService: ShopifyProductService;
|
|
16
|
+
|
|
17
|
+
constructor(private axiosInstance: AxiosInstance) {
|
|
18
|
+
super();
|
|
19
|
+
this.ShopifyProductService = new ShopifyProductService(axiosInstance);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public create = async (collection: ICollection): Promise<ICollection> => {
|
|
23
|
+
return new Promise<ICollection>(async (resolve, reject) => {
|
|
24
|
+
try {
|
|
25
|
+
Logger.info('Creating collection', collection);
|
|
26
|
+
const response = await this.axiosInstance.post('/custom_collections.json', { custom_collection: collection });
|
|
27
|
+
resolve(response.data.custom_collection);
|
|
28
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a collect at the end of the collection
|
|
34
|
+
*
|
|
35
|
+
* @param {ICollect} collect
|
|
36
|
+
* @returns Promise
|
|
37
|
+
*/
|
|
38
|
+
public createCollect = async (collect: ICollect): Promise<ICollect> => {
|
|
39
|
+
return new Promise<ICollect>(async (resolve, reject) => {
|
|
40
|
+
try {
|
|
41
|
+
Logger.info('Creating collect', collect);
|
|
42
|
+
const response = await this.axiosInstance.post('/collects.json', { collect: collect });
|
|
43
|
+
resolve(response.data.collect);
|
|
44
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a collect at a certain position in the collection
|
|
50
|
+
*
|
|
51
|
+
* @param {collectionId}
|
|
52
|
+
* @param {productId}
|
|
53
|
+
* @param {position} Position within the collection
|
|
54
|
+
* @returns Promise
|
|
55
|
+
*/
|
|
56
|
+
public createCollectAtPosition = async (collectionId: number, productId: number, position: number): Promise<void> => {
|
|
57
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
58
|
+
try {
|
|
59
|
+
Logger.info(`Creating collect for product ${productId} and collection ${collectionId} at position ${position}`);
|
|
60
|
+
await this.axiosInstance.put(`/custom_collections/${collectionId}.json`, { custom_collection: {id: collectionId, collects: [{ product_id: productId, position: position}] } });
|
|
61
|
+
resolve();
|
|
62
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Removes all collects from Shopify.
|
|
68
|
+
*
|
|
69
|
+
* @param {number} collectionId
|
|
70
|
+
* @param {number} interval in ms of every delete call to Shopify. Set to 0 or leave it empty to not queue the calls.
|
|
71
|
+
* @returns Promise
|
|
72
|
+
*/
|
|
73
|
+
public removeCollects = async (collectionId: number, interval?: number): Promise<void> => {
|
|
74
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
75
|
+
try {
|
|
76
|
+
Logger.info(`Delete All collects for collection with ID: ${collectionId} and using interval of ${interval} between API calls`);
|
|
77
|
+
const collects = await this.getCollects(collectionId);
|
|
78
|
+
|
|
79
|
+
if(interval && interval > 0) {
|
|
80
|
+
const collectsQueue = new TimerQueue({ interval: interval, timeout: 10000, retry: 3, retryInterval: 500, autostart: false});
|
|
81
|
+
for (const collect of collects) {
|
|
82
|
+
collectsQueue.push( async () => {
|
|
83
|
+
Logger.info(`Removing collect ${collect.id} for collection ${collectionId}`);
|
|
84
|
+
await this.axiosInstance.delete(`/collects/${collect.id}.json`);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
collectsQueue.on('end', () => {
|
|
89
|
+
Logger.info('End Collects Remove Queue');
|
|
90
|
+
resolve();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
collectsQueue.on('error', () => {
|
|
94
|
+
Logger.info('End Collects Queue with errors');
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
collectsQueue.start();
|
|
99
|
+
} else {
|
|
100
|
+
for (const collect of collects) {
|
|
101
|
+
Logger.info(`Removing collect ${collect.id} for collection ${collectionId} without Queue`);
|
|
102
|
+
await this.axiosInstance.delete(`/collects/${collect.id}.json`);
|
|
103
|
+
}
|
|
104
|
+
resolve();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Removes a collect from a Shopify collection.
|
|
113
|
+
*
|
|
114
|
+
* @param {number} collectId
|
|
115
|
+
* @returns Promise
|
|
116
|
+
*/
|
|
117
|
+
public removeCollect = async (collectId: number): Promise<void> => {
|
|
118
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
119
|
+
try {
|
|
120
|
+
Logger.info(`Delete a collect for collection with ID: ${collectId}`);
|
|
121
|
+
await this.axiosInstance.delete(`/collects/${collectId}.json`);
|
|
122
|
+
resolve();
|
|
123
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Removes a list of products from a collection.
|
|
129
|
+
*
|
|
130
|
+
* @param {number} collectionId
|
|
131
|
+
* @param {number[]} products
|
|
132
|
+
* @returns Promise
|
|
133
|
+
*/
|
|
134
|
+
public removeProducts = async (collectionId: number, products: number[]): Promise<void> => {
|
|
135
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
136
|
+
try {
|
|
137
|
+
Logger.info(`Delete products from collection with ID: ${collectionId}`);
|
|
138
|
+
if(products && products.length > 0) {
|
|
139
|
+
const productschunks: number[][] = this.sliceIntoChunks<number[]>(products, 249);
|
|
140
|
+
const mutation = gql`
|
|
141
|
+
mutation collectionRemoveProducts($id: ID!, $productIds: [ID!]!) {
|
|
142
|
+
collectionRemoveProducts(id: $id, productIds: $productIds) {
|
|
143
|
+
job {
|
|
144
|
+
id
|
|
145
|
+
}
|
|
146
|
+
userErrors {
|
|
147
|
+
field
|
|
148
|
+
message
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
for(const productschunk of productschunks) {
|
|
154
|
+
const response = await this.axiosInstance.post('/graphql.json', {
|
|
155
|
+
query: print(mutation),
|
|
156
|
+
variables: {
|
|
157
|
+
id: `gid://shopify/Collection/${collectionId}`,
|
|
158
|
+
productIds: productschunk.map((pId) => `gid://shopify/Product/${pId}`)
|
|
159
|
+
}
|
|
160
|
+
}, { query_cost: 10 });
|
|
161
|
+
if(response.data.errors && response.data.errors.length > 0) {
|
|
162
|
+
throw new Error(JSON.stringify(response.data.errors));
|
|
163
|
+
}
|
|
164
|
+
Logger.info(`Deleted product's chunck from collection with ID: ${collectionId}`, JSON.stringify(response.data));
|
|
165
|
+
}
|
|
166
|
+
Logger.info(`END Deleted products fromn collection with ID: ${collectionId}`);
|
|
167
|
+
} else {
|
|
168
|
+
Logger.info(`No product to delete from collection with ID: ${collectionId}`);
|
|
169
|
+
}
|
|
170
|
+
resolve();
|
|
171
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Adds automatic discount x for y on a collection
|
|
177
|
+
*
|
|
178
|
+
* @param {number} collectionId
|
|
179
|
+
* @param {number[]} products
|
|
180
|
+
* @returns Promise
|
|
181
|
+
*/
|
|
182
|
+
public addBuyXGetYDiscount = async (collectionId: number, productsToBuy: number, productsToGet: number): Promise<{ id: number, title: string}> => {
|
|
183
|
+
return new Promise<{id: number; title: string; }>(async (resolve, reject) => {
|
|
184
|
+
try {
|
|
185
|
+
Logger.info(`Adds discount into collection with ID: ${collectionId}, Buy ${productsToBuy} Get ${productsToGet}`);
|
|
186
|
+
if(productsToBuy && productsToBuy > 0 && productsToGet && productsToGet > 0) {
|
|
187
|
+
const mutation = gql`
|
|
188
|
+
mutation {
|
|
189
|
+
discountAutomaticBxgyCreate(automaticBxgyDiscount: {
|
|
190
|
+
title: "BXGY discount test",
|
|
191
|
+
startsAt: "${moment().format('YYYY-MM-DD')}",
|
|
192
|
+
customerBuys: {
|
|
193
|
+
value: {
|
|
194
|
+
quantity: "${productsToBuy}"
|
|
195
|
+
}
|
|
196
|
+
items: {
|
|
197
|
+
collections: {
|
|
198
|
+
add: ["gid://shopify/Collection/${collectionId}"]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
customerGets: {
|
|
203
|
+
value: {
|
|
204
|
+
discountOnQuantity: {
|
|
205
|
+
quantity: "${productsToGet}",
|
|
206
|
+
effect: {
|
|
207
|
+
percentage: 1.00
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
items: {
|
|
212
|
+
collections: {
|
|
213
|
+
add: ["gid://shopify/Collection/${collectionId}"]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}}) {
|
|
217
|
+
userErrors {
|
|
218
|
+
field
|
|
219
|
+
message
|
|
220
|
+
code
|
|
221
|
+
}
|
|
222
|
+
automaticDiscountNode {
|
|
223
|
+
automaticDiscount {
|
|
224
|
+
... on DiscountAutomaticBxgy {
|
|
225
|
+
title
|
|
226
|
+
summary
|
|
227
|
+
status
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
`;
|
|
234
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(mutation) }, { query_cost: 15 });
|
|
235
|
+
if(response.data.errors && response.data.errors.length > 0) {
|
|
236
|
+
throw new Error(JSON.stringify(response.data.errors));
|
|
237
|
+
}
|
|
238
|
+
if(response.data.discountAutomaticBxgyCreate.userErrors && response.data.discountAutomaticBxgyCreate.userErrors.length > 0) {
|
|
239
|
+
throw new Error(JSON.stringify(response.data.discountAutomaticBxgyCreate.userErrors));
|
|
240
|
+
}
|
|
241
|
+
Logger.info(`Add discount to collection with ID: ${collectionId}`, JSON.stringify(response.data));
|
|
242
|
+
resolve({ id: this.getIdFromGraphId(response.data.discountAutomaticBxgyCreate.automaticDiscountNode.automaticDiscount.id), title: response.data.discountAutomaticBxgyCreate.automaticDiscountNode.automaticDiscount.title });
|
|
243
|
+
} else {
|
|
244
|
+
Logger.info(`No products to buy or to get applied to delete from collection with ID: ${collectionId}`);
|
|
245
|
+
}
|
|
246
|
+
resolve(null);
|
|
247
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Overwrites Collection Metafield
|
|
253
|
+
*
|
|
254
|
+
* @param {number} collectionId
|
|
255
|
+
* @param {string} metafieldVal
|
|
256
|
+
* @param {string} namespace
|
|
257
|
+
* @param {string} key
|
|
258
|
+
* @returns Promise
|
|
259
|
+
*/
|
|
260
|
+
public updateMetafield = async (collectionId: number, metafieldVal: string, namespace: string, key: string): Promise<{id: number;}> => {
|
|
261
|
+
return new Promise<{id: number;}>(async (resolve, reject) => {
|
|
262
|
+
try {
|
|
263
|
+
Logger.info(`Collection ${collectionId} updated with ${metafieldVal}`);
|
|
264
|
+
const metafieldQuery = gql `mutation MetafieldsSet($collectionId: ID!, $value: String!) {
|
|
265
|
+
metafieldsSet(metafields: [
|
|
266
|
+
{
|
|
267
|
+
ownerId: $collectionId
|
|
268
|
+
namespace: "${namespace}"
|
|
269
|
+
key: "${key}"
|
|
270
|
+
value: $value
|
|
271
|
+
type: "json"
|
|
272
|
+
}
|
|
273
|
+
]) {
|
|
274
|
+
metafields {
|
|
275
|
+
id
|
|
276
|
+
}
|
|
277
|
+
userErrors {
|
|
278
|
+
message
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}`;
|
|
282
|
+
|
|
283
|
+
const metafieldResponse = await this.axiosInstance.post('/graphql.json', { query: print(metafieldQuery),
|
|
284
|
+
variables: { collectionId: this.getGraphCollectionIdFromId(collectionId), value: metafieldVal }}, { query_cost: 803 });
|
|
285
|
+
|
|
286
|
+
if(metafieldResponse.data.errors) {
|
|
287
|
+
reject(`Error ${JSON.stringify(metafieldResponse.data.errors)}`);
|
|
288
|
+
} else {
|
|
289
|
+
Logger.info(`Meta coll response ${JSON.stringify(metafieldResponse.data)}`);
|
|
290
|
+
resolve({ id: collectionId });
|
|
291
|
+
}
|
|
292
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Gets metafield of a collection
|
|
298
|
+
*
|
|
299
|
+
* @param {number} collectioId
|
|
300
|
+
* @param {string} namespace
|
|
301
|
+
* @param {string} key
|
|
302
|
+
* @returns Promise
|
|
303
|
+
*/
|
|
304
|
+
public getMetafield = async (collectioId: number, namespace: string, key: string): Promise<IMetafield[]> => {
|
|
305
|
+
return new Promise<IMetafield[]>(async (resolve, reject) => {
|
|
306
|
+
try {
|
|
307
|
+
Logger.info(`Getting metafield for collectio id -> ${collectioId}`);
|
|
308
|
+
const response = await this.axiosInstance.get(`/collections/${collectioId}/metafields.json?key=${key}&namespace=${namespace}`);
|
|
309
|
+
resolve(response.data.metafields);
|
|
310
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
public getCollection = async (collectionId: number): Promise<ICollection> => {
|
|
315
|
+
return new Promise<ICollection>(async (resolve, reject) => {
|
|
316
|
+
try {
|
|
317
|
+
Logger.info('Getting collection', collectionId);
|
|
318
|
+
const collectionResponse = await this.axiosInstance.get(`/collections/${collectionId}.json`);
|
|
319
|
+
const collection = collectionResponse.data.collection;
|
|
320
|
+
resolve(collection);
|
|
321
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
public getCollects = async (collectionId: number, productLimit?: number): Promise<ICollect[]> => {
|
|
326
|
+
return new Promise<ICollect[]>(async (resolve, reject) => {
|
|
327
|
+
try {
|
|
328
|
+
Logger.info('Getting collects for collection', collectionId);
|
|
329
|
+
let collects: ICollect[] = [];
|
|
330
|
+
const countResponse = await this.axiosInstance.get(`/collects/count.json?collection_id=${collectionId}`);
|
|
331
|
+
const count = countResponse.data.count;
|
|
332
|
+
Logger.info(`Counted collects ${count}`);
|
|
333
|
+
while (collects.length < count) {
|
|
334
|
+
let getCollectsUrl = `/collects.json?collection_id=${collectionId}&limit=250`;
|
|
335
|
+
if(collects.length > 1){
|
|
336
|
+
getCollectsUrl += `&since_id=${collects[collects.length - 1].id}`;
|
|
337
|
+
} else {
|
|
338
|
+
getCollectsUrl += '&since_id=0';
|
|
339
|
+
}
|
|
340
|
+
Logger.info(`Get collects URL ${getCollectsUrl}`);
|
|
341
|
+
const collectsRetrieved = await this.axiosInstance.get(getCollectsUrl);
|
|
342
|
+
collects = collects.concat(collectsRetrieved.data.collects);
|
|
343
|
+
if(productLimit && collects.length > productLimit) {
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
resolve(collects);
|
|
348
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
public getProductIdsOfCollection = async (collectionId: number, productLimit: number): Promise<number[]> => {
|
|
353
|
+
return new Promise<number[]>(async (resolve, reject) => {
|
|
354
|
+
try {
|
|
355
|
+
Logger.info('Getting product Ids for collection', collectionId);
|
|
356
|
+
let productIds: number[] = [];
|
|
357
|
+
const firstResponse = await this.axiosInstance.get(`/collections/${collectionId}/products.json?fields=id&limit=100`);
|
|
358
|
+
if(firstResponse && firstResponse.data.products && firstResponse.data.products.length > 0) {
|
|
359
|
+
productIds = productIds.concat(firstResponse.data.products.map( (p) => p.id));
|
|
360
|
+
}
|
|
361
|
+
const links: string = firstResponse.headers['link'];
|
|
362
|
+
Logger.info(`Links ${links}`);
|
|
363
|
+
if(links){
|
|
364
|
+
let parsedLinks = parseLink(links);
|
|
365
|
+
if (parsedLinks['next']) {
|
|
366
|
+
while(parsedLinks && parsedLinks['next']) {
|
|
367
|
+
const response = await this.axiosInstance.get(parsedLinks['next'].url);
|
|
368
|
+
const nlinks = response.headers['link'];
|
|
369
|
+
Logger.info(`Links ${nlinks} ${parsedLinks['next'].url}`);
|
|
370
|
+
if(nlinks) {
|
|
371
|
+
parsedLinks = parseLink(nlinks);
|
|
372
|
+
} else {
|
|
373
|
+
parsedLinks = null;
|
|
374
|
+
}
|
|
375
|
+
if(response && response.data.products && response.data.products.length > 0) {
|
|
376
|
+
productIds = productIds.concat(response.data.products.map( (p) => p.id));
|
|
377
|
+
}
|
|
378
|
+
if(productIds.length > productLimit) {
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
resolve(productIds);
|
|
385
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
public get = async (num: number): Promise<IProduct[]> => {
|
|
390
|
+
return new Promise<IProduct[]>(async (resolve, reject) => {
|
|
391
|
+
try {
|
|
392
|
+
Logger.info('getting customers');
|
|
393
|
+
let products: IProduct[] = [];
|
|
394
|
+
const response = await this.axiosInstance.get('/products/count.json');
|
|
395
|
+
const count = response.data.count > num ? num : response.data.count;
|
|
396
|
+
while (products.length < count) {
|
|
397
|
+
let productsGetURL = '/products.json?limit=250&since_id=';
|
|
398
|
+
if(products.length > 1) {
|
|
399
|
+
productsGetURL += `${products[products.length -1].id}`;
|
|
400
|
+
} else {
|
|
401
|
+
productsGetURL += '0';
|
|
402
|
+
}
|
|
403
|
+
const productsRetrieved = await this.axiosInstance.get(productsGetURL);
|
|
404
|
+
products = products.concat(productsRetrieved.data.orders);
|
|
405
|
+
}
|
|
406
|
+
resolve(products.slice(0,count));
|
|
407
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
public getAll = async (collectionType: 'smart' | 'custom'): Promise<ICollection[]> => {
|
|
412
|
+
return new Promise<ICollection[]>(async (resolve, reject) => {
|
|
413
|
+
try {
|
|
414
|
+
Logger.info(`getting ${collectionType} collections`);
|
|
415
|
+
const response = await this.axiosInstance.get(`/${collectionType}_collections/count.json`);
|
|
416
|
+
const count = response.data.count;
|
|
417
|
+
Logger.info(`Getting ${count} collections`);
|
|
418
|
+
let collections: ICollection[] = [];
|
|
419
|
+
while (collections.length < count) {
|
|
420
|
+
let collectionsGetURL = `/${collectionType}_collections.json?limit=250&since_id=`;
|
|
421
|
+
if(collections.length > 1) {
|
|
422
|
+
collectionsGetURL += collections[collections.length - 1].id;
|
|
423
|
+
} else {
|
|
424
|
+
collectionsGetURL += '0';
|
|
425
|
+
}
|
|
426
|
+
const currentPage = (parseInt((collections.length / 250).toFixed(0)) + 1);
|
|
427
|
+
Logger.info(`Collections Get URL -> ${collectionsGetURL}`);
|
|
428
|
+
const collectionsRetrieved = await this.axiosInstance.get(collectionsGetURL);
|
|
429
|
+
collections = collections.concat(collectionsRetrieved.data[`${collectionType}_collections`]);
|
|
430
|
+
Logger.info('Collections retrieved -> ' + collections.length);
|
|
431
|
+
const nextPage = (parseInt((collections.length / 250).toFixed(0)) + 1);
|
|
432
|
+
if(currentPage === nextPage) {
|
|
433
|
+
Logger.info('Collections next page is the current page so, we are breaking the loop!');
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
resolve(collections);
|
|
438
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
public delete = async (id: number): Promise<void> => {
|
|
443
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
444
|
+
try {
|
|
445
|
+
Logger.info(`deleting custom collections ${id}`);
|
|
446
|
+
await this.axiosInstance.delete(`/custom_collections/${id}.json`);
|
|
447
|
+
resolve();
|
|
448
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
public deleteProductsWithTag = async (collectionId: number, productTag: string): Promise<void> => {
|
|
453
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
454
|
+
try {
|
|
455
|
+
const collects: ICollect[] = await this.getCollects(collectionId);
|
|
456
|
+
collects.forEach(async collect => {
|
|
457
|
+
const product:IProduct = await this.ShopifyProductService.list.getById(collect.product_id, { add_body_html: false, add_images: false, add_options: false, add_tags: true, add_title: false, add_variants: false, add_weight: false });
|
|
458
|
+
let deleteProduct = false;
|
|
459
|
+
if (product.tags) {
|
|
460
|
+
Logger.info(`Product ID: ${product.id} has tags: ${product.tags}`);
|
|
461
|
+
(product.tags as string[]).forEach((tag) => {
|
|
462
|
+
if (tag.trim() === productTag) {
|
|
463
|
+
deleteProduct = true;
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (deleteProduct) {
|
|
468
|
+
Logger.info(`Product ID: ${product.id} has tag ${productTag} so we should delete it`);
|
|
469
|
+
await this.ShopifyProductService.delete(collect.product_id);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
resolve();
|
|
473
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
public update = async (collection: ICollection): Promise<void> => {
|
|
478
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
479
|
+
try {
|
|
480
|
+
Logger.info(`Updating custom collections ${collection.id}`);
|
|
481
|
+
await this.axiosInstance.put(`/custom_collections/${collection.id}.json`, { custom_collection: collection });
|
|
482
|
+
resolve();
|
|
483
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
public updateCollects = async (collection: ICollection): Promise<void> => {
|
|
488
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
489
|
+
try {
|
|
490
|
+
Logger.info(`Updating custom collections ${collection.id}`);
|
|
491
|
+
if(!collection.collects) {
|
|
492
|
+
collection.collects = [];
|
|
493
|
+
}
|
|
494
|
+
const collectChunks = this.sliceIntoChunks<ICollect[]>(collection.collects, 500);
|
|
495
|
+
for(const collectChunk of collectChunks) {
|
|
496
|
+
Logger.info(`Updating custom collection chunk with ${collectChunk.length} id${collection.id}`);
|
|
497
|
+
await this.axiosInstance.put(`/custom_collections/${collection.id}.json`, { custom_collection: { id: collection.id, collects: collectChunk } });
|
|
498
|
+
}
|
|
499
|
+
resolve();
|
|
500
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
501
|
+
});
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
public publish = async (id: number): Promise<void> => {
|
|
505
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
506
|
+
try {
|
|
507
|
+
Logger.info(`Publish custom collections ${id}`);
|
|
508
|
+
await this.update({ id: id, published: true });
|
|
509
|
+
resolve();
|
|
510
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
511
|
+
});
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
public unpublish = async (id: number): Promise<void> => {
|
|
515
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
516
|
+
try {
|
|
517
|
+
Logger.info(`UnPublish custom collections ${id}`);
|
|
518
|
+
await this.update({ id: id, published: false });
|
|
519
|
+
resolve();
|
|
520
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
521
|
+
});
|
|
522
|
+
};
|
|
523
|
+
}
|