@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.
Files changed (71) hide show
  1. package/Logger.ts +18 -0
  2. package/README.md +258 -0
  3. package/addTypings.sh +2 -0
  4. package/bitbucket-pipelines.yml +38 -0
  5. package/index.ts +132 -0
  6. package/package.json +57 -0
  7. package/publish.sh +20 -0
  8. package/services/CacheWrapper.ts +30 -0
  9. package/services/CountryCodeService.ts +507 -0
  10. package/shopify/ShopifyAppService.ts +109 -0
  11. package/shopify/ShopifyAssetService.ts +20 -0
  12. package/shopify/ShopifyBillingService.ts +73 -0
  13. package/shopify/ShopifyCartTrasnformationService.ts +207 -0
  14. package/shopify/ShopifyCollectionService.ts +523 -0
  15. package/shopify/ShopifyCustomerService.ts +472 -0
  16. package/shopify/ShopifyDeliveryCustomisationService.ts +220 -0
  17. package/shopify/ShopifyDiscountService.ts +131 -0
  18. package/shopify/ShopifyDraftOrderService.ts +125 -0
  19. package/shopify/ShopifyFulfillmentService.ts +41 -0
  20. package/shopify/ShopifyFunctionsProductDiscountsService.ts +166 -0
  21. package/shopify/ShopifyInventoryService.ts +415 -0
  22. package/shopify/ShopifyLocationService.ts +29 -0
  23. package/shopify/ShopifyOrderRefundsService.ts +138 -0
  24. package/shopify/ShopifyOrderRiskService.ts +19 -0
  25. package/shopify/ShopifyOrderService.ts +1143 -0
  26. package/shopify/ShopifyPageService.ts +62 -0
  27. package/shopify/ShopifyProductService.ts +772 -0
  28. package/shopify/ShopifyShippingZonesService.ts +37 -0
  29. package/shopify/ShopifyShopService.ts +101 -0
  30. package/shopify/ShopifyTemplateService.ts +30 -0
  31. package/shopify/ShopifyThemeService.ts +33 -0
  32. package/shopify/ShopifyUtils.ts +56 -0
  33. package/shopify/ShopifyWebhookService.ts +110 -0
  34. package/shopify/base/APIVersion.ts +4 -0
  35. package/shopify/base/AbstractService.ts +152 -0
  36. package/shopify/base/ErrorHelper.ts +24 -0
  37. package/shopify/errors/InspiraShopifyCustomError.ts +7 -0
  38. package/shopify/errors/InspiraShopifyError.ts +15 -0
  39. package/shopify/errors/InspiraShopifyUnableToReserveInventoryError.ts +7 -0
  40. package/shopify/helpers/ShopifyProductServiceHelper.ts +450 -0
  41. package/shopify/product/ShopifyProductCountService.ts +110 -0
  42. package/shopify/product/ShopifyProductListService.ts +333 -0
  43. package/shopify/product/ShopifyProductMetafieldsService.ts +405 -0
  44. package/shopify/product/ShopifyProductPublicationsService.ts +112 -0
  45. package/shopify/product/ShopifyVariantService.ts +584 -0
  46. package/shopify/router/ShopifyMandatoryRouter.ts +37 -0
  47. package/shopify/router/ShopifyRouter.ts +85 -0
  48. package/shopify/router/ShopifyRouterBis.ts +85 -0
  49. package/shopify/router/ShopifyRouterBisBis.ts +85 -0
  50. package/shopify/router/ShopifyRouterBisBisBis.ts +85 -0
  51. package/shopify/router/ShopifyRouterBisBisBisBis.ts +85 -0
  52. package/shopify/router/WebhookSkipMiddleware.ts +73 -0
  53. package/shopify/router/services/CryptoService.ts +26 -0
  54. package/shopify/router/services/HmacValidator.ts +36 -0
  55. package/shopify/router/services/OauthService.ts +17 -0
  56. package/shopify/router/services/RestUtils.ts +13 -0
  57. package/shopify/router/services/rateLimiter/MemoryStores.ts +46 -0
  58. package/shopify/router/services/rateLimiter/StoreRateLimiter.ts +46 -0
  59. package/test/README.md +223 -0
  60. package/test/router/ShopifyRouter.test.ts +71 -0
  61. package/test/router/WebhookSkipMiddleware.test.ts +86 -0
  62. package/test/router/services/HmacValidator.test.ts +24 -0
  63. package/test/router/services/RestUtils.test.ts +13 -0
  64. package/test/router/services/rateLimiter/StoreRateLimiter.test.ts +62 -0
  65. package/test/services/CacheWrapper.test.ts +30 -0
  66. package/test/shopify/ShopifyOrderService.test.ts +29 -0
  67. package/test/shopify/ShopifyProductService.test.ts +118 -0
  68. package/test/shopify/ShopifyWebhookService.test.ts +105 -0
  69. package/tsconfig.json +10 -0
  70. package/typings/axios.d.ts +8 -0
  71. 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
+ }