feed-common 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,369 @@
1
+ import { ProductUploadMapSource } from '../types/profile.types.js';
2
+ import { ProductUploadRuleFilterType } from '../types/profile.types.js';
3
+
4
+ export const SOURCE_VENDORS = 'source.vendor';
5
+ export const SOURCE_TAGS = 'source.tag';
6
+ export const SOURCE_PORODUCT_TYPE = 'source.product_type';
7
+ export const SOURCE_COLLECTIONS = 'source.collection';
8
+ export const SOURCE_LANGUAGE = 'source.language';
9
+ export const SOURCE_GOOGLE_CATEGORY = 'source.google_category';
10
+ export const SOURCE_COUNTRY = 'source.country';
11
+
12
+ export const SOURCE_SHOPIFY_VENDORS = 'shopify.vendor';
13
+ export const SOURCE_SHOPIFY_BARCODE = 'shopify.barcode';
14
+ export const SOURCE_SHOPIFY_SKU = 'shopify.sku';
15
+
16
+ export const SOURCE_CUSTOM = 'custom_input';
17
+
18
+ export const RuleOperators = {
19
+ contains: {
20
+ label: 'Contains',
21
+ value: 'contains',
22
+ },
23
+ notContains: {
24
+ label: 'Not Contains',
25
+ value: 'notContains',
26
+ },
27
+ equals: {
28
+ label: 'Equals',
29
+ value: 'equals',
30
+ },
31
+ notEquals: {
32
+ label: 'Not Equals',
33
+ value: 'notEquals',
34
+ },
35
+ startsWith: {
36
+ label: 'Starts With',
37
+ value: 'startsWith',
38
+ },
39
+ notStartsWith: {
40
+ label: 'Not Starts With',
41
+ value: 'notStartsWith',
42
+ },
43
+ endsWith: {
44
+ label: 'Ends With',
45
+ value: 'endsWith',
46
+ },
47
+ notEndsWith: {
48
+ label: 'Not Ends With',
49
+ value: 'notEndsWith',
50
+ },
51
+ in: {
52
+ label: 'In',
53
+ value: 'in',
54
+ },
55
+ notIn: {
56
+ label: 'Not In',
57
+ value: 'notIn',
58
+ },
59
+ };
60
+
61
+ export const PruoductUploadRulesOperators = {
62
+ string: [
63
+ RuleOperators.contains,
64
+ RuleOperators.notContains,
65
+ RuleOperators.equals,
66
+ RuleOperators.notEquals,
67
+ RuleOperators.startsWith,
68
+ RuleOperators.endsWith,
69
+ RuleOperators.notStartsWith,
70
+ RuleOperators.notEndsWith,
71
+ ],
72
+ list: [RuleOperators.in, RuleOperators.notIn],
73
+ };
74
+
75
+ export const ProductUploadRuleFilters: ProductUploadRuleFilterType[] = [
76
+ {
77
+ label: 'Title',
78
+ operators: PruoductUploadRulesOperators.string,
79
+ value: 'title',
80
+ },
81
+ {
82
+ label: 'Collection',
83
+ operators: PruoductUploadRulesOperators.list,
84
+ value: 'collection',
85
+ },
86
+ {
87
+ label: 'Product Type',
88
+ operators: PruoductUploadRulesOperators.list,
89
+ value: 'product_type',
90
+ },
91
+ {
92
+ label: 'Vendor',
93
+ operators: PruoductUploadRulesOperators.list,
94
+ value: 'vendor',
95
+ },
96
+ {
97
+ label: 'Tag',
98
+ operators: PruoductUploadRulesOperators.list,
99
+ value: 'tag',
100
+ },
101
+ ];
102
+
103
+ const ShopifyProductSource = {
104
+ vendor: { label: 'Shopify Product Vendor', value: SOURCE_SHOPIFY_VENDORS },
105
+ barcode: { label: 'Shopify Product Barcode', value: SOURCE_SHOPIFY_BARCODE },
106
+ sku: { label: 'Shopify Product SKU', value: SOURCE_SHOPIFY_SKU },
107
+ custom: { label: 'Custom input', value: SOURCE_CUSTOM },
108
+ };
109
+
110
+ export const ProductUploadMappings: ProductUploadMapSource[] = [
111
+ {
112
+ required: true,
113
+ label: 'Content Language',
114
+ attribute: 'contentLanguage',
115
+ type: 'select',
116
+ choises: [SOURCE_LANGUAGE],
117
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
118
+ description: 'Language for the item',
119
+ },
120
+ {
121
+ required: true,
122
+ label: 'Target Country',
123
+ attribute: 'targetCountry',
124
+ type: 'select',
125
+ choises: [SOURCE_COUNTRY],
126
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
127
+ description: 'The item\'s country of sale',
128
+ },
129
+ {
130
+ required: false,
131
+ label: 'Adult',
132
+ attribute: 'adult',
133
+ type: 'select',
134
+ choises: [
135
+ { label: 'Yes', value: true },
136
+ { label: 'No', value: false },
137
+ ],
138
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
139
+ description:
140
+ 'Should be set to "Yes" if the item is targeted towards adults',
141
+ },
142
+ {
143
+ required: false,
144
+ label: 'Age Group',
145
+ attribute: 'ageGroup',
146
+ type: 'select',
147
+ choises: [
148
+ { label: '0-3 months old', value: 'newborn' },
149
+ { label: '3-12 months old', value: 'infant' },
150
+ { label: '1-5 years old', value: 'toddler' },
151
+ { label: '5-13 years old', value: 'kids' },
152
+ { label: '13 years old or more', value: 'adult' },
153
+ ],
154
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
155
+ description: 'Target age group of the item',
156
+ },
157
+ {
158
+ required: false,
159
+ label: 'Availability',
160
+ attribute: 'availability',
161
+ type: 'select',
162
+ choises: [
163
+ { label: 'In Stock', value: 'in_stock' },
164
+ { label: 'Out Of Stock', value: 'out_of_stock' },
165
+ { label: 'Preorder', value: 'preorder' },
166
+ { label: 'Backorder', value: 'backorder' },
167
+ ],
168
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
169
+ description: 'Availability status of the item',
170
+ },
171
+ {
172
+ required: false,
173
+ label: 'Brand',
174
+ attribute: 'brand',
175
+ type: 'select',
176
+ defaultValue: 'shopify.vendor',
177
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
178
+ description: 'Brand of the item',
179
+ choises: [
180
+ ShopifyProductSource.vendor,
181
+ SOURCE_VENDORS,
182
+ ShopifyProductSource.custom,
183
+ ],
184
+ },
185
+ {
186
+ required: false,
187
+ label: 'Gender',
188
+ attribute: 'gender',
189
+ type: 'select',
190
+ choises: [
191
+ { label: 'Male', value: 'male' },
192
+ { label: 'Female', value: 'female' },
193
+ { label: 'Unisex', value: 'unisex' },
194
+ ],
195
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
196
+ description: 'Target gender of the item',
197
+ },
198
+ {
199
+ required: false,
200
+ label: 'Google category',
201
+ attribute: 'googleProductCategory',
202
+ type: 'select',
203
+ choises: [SOURCE_GOOGLE_CATEGORY],
204
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
205
+ description: 'Google\'s category of the item',
206
+ },
207
+ {
208
+ required: false,
209
+ label: 'GTIN',
210
+ attribute: 'gtin',
211
+ type: 'select',
212
+ defaultValue: 'shopify.vendor',
213
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
214
+ description: 'Global Trade Item Number (GTIN) of the item',
215
+ choises: [
216
+ ShopifyProductSource.barcode,
217
+ ShopifyProductSource.sku,
218
+ { label: 'Custom input', value: SOURCE_CUSTOM },
219
+ ],
220
+ },
221
+ {
222
+ required: false,
223
+ label: 'MPN',
224
+ attribute: 'mpn',
225
+ type: 'select',
226
+ defaultValue: 'shopify.vendor',
227
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
228
+ description: 'Manufacturer Part Number (MPN) of the item',
229
+ choises: [
230
+ ShopifyProductSource.barcode,
231
+ ShopifyProductSource.sku,
232
+ { label: 'Custom input', value: SOURCE_CUSTOM },
233
+ ],
234
+ },
235
+ {
236
+ required: false,
237
+ label: 'Included Destinations',
238
+ attribute: 'includedDestinations',
239
+ type: 'multiselect',
240
+ choises: [
241
+ { label: 'Shopping ads', value: 'Shopping_ads' },
242
+ { label: 'Dynamic remarketing ads', value: 'Display_ads' },
243
+ { label: 'Free listings', value: 'Free_listings' },
244
+ ],
245
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
246
+ description:
247
+ 'The list of destinations to include for this target (corresponds to checked check boxes in Merchant Center)',
248
+ },
249
+ {
250
+ required: false,
251
+ label: 'Shipping label',
252
+ attribute: 'shippingLabel',
253
+ type: 'text',
254
+ rules: ['title', 'collection', 'product_type', 'vendor', 'tag'],
255
+ description:
256
+ 'The shipping label of the product, used to group product in account-level shipping rules',
257
+ },
258
+
259
+ // /**
260
+ // * The day a pre-ordered product becomes available for delivery, in ISO 8601 format.
261
+ // */
262
+ // availabilityDate?: string | null;
263
+ // /**
264
+ // * Color of the item.
265
+ // */
266
+ // color?: string | null;
267
+ // /**
268
+ // * Condition or state of the item.
269
+ // */
270
+ // condition?: string | null;
271
+ // /**
272
+ // * Custom label 0 for custom grouping of items in a Shopping campaign.
273
+ // */
274
+ // customLabel0?: string | null;
275
+ // /**
276
+ // * Custom label 1 for custom grouping of items in a Shopping campaign.
277
+ // */
278
+ // customLabel1?: string | null;
279
+ // /**
280
+ // * Custom label 2 for custom grouping of items in a Shopping campaign.
281
+ // */
282
+ // customLabel2?: string | null;
283
+ // /**
284
+ // * Custom label 3 for custom grouping of items in a Shopping campaign.
285
+ // */
286
+ // customLabel3?: string | null;
287
+ // /**
288
+ // * Custom label 4 for custom grouping of items in a Shopping campaign.
289
+ // */
290
+ // customLabel4?: string | null;
291
+ // /**
292
+ // * The date time when an offer becomes visible in search results across Google’s YouTube surfaces, in [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) format. See [Disclosure date](https://support.google.com/merchants/answer/13034208) for more information.
293
+ // */
294
+ // disclosureDate?: string | null;
295
+ // /**
296
+ // * Date on which the item should expire, as specified upon insertion, in ISO 8601 format. The actual expiration date in Google Shopping is exposed in `productstatuses` as `googleExpirationDate` and might be earlier if `expirationDate` is too far in the future.
297
+ // */
298
+ // expirationDate?: string | null;
299
+ // /**
300
+ // * The material of which the item is made.
301
+ // */
302
+ // material?: string | null;
303
+ // /**
304
+ // * Maximal product handling time (in business days).
305
+ // */
306
+ // maxHandlingTime?: string | null;
307
+ // /**
308
+ // * Minimal product handling time (in business days).
309
+ // */
310
+ // minHandlingTime?: string | null;
311
+ // /**
312
+ // * The pick up option for the item. Acceptable values are: - "`buy`" - "`reserve`" - "`ship to store`" - "`not supported`"
313
+ // */
314
+ // pickupMethod?: string | null;
315
+ // /**
316
+ // * Item store pickup timeline. Acceptable values are: - "`same day`" - "`next day`" - "`2-day`" - "`3-day`" - "`4-day`" - "`5-day`" - "`6-day`" - "`7-day`" - "`multi-week`"
317
+ // */
318
+ // pickupSla?: string | null;
319
+ // /**
320
+ // * The height of the product in the units provided. The value must be between 0 (exclusive) and 3000 (inclusive).
321
+ // */
322
+ // productHeight?: Schema$ProductDimension;
323
+ // /**
324
+ // * The length of the product in the units provided. The value must be between 0 (exclusive) and 3000 (inclusive).
325
+ // */
326
+ // productLength?: Schema$ProductDimension;
327
+ // /**
328
+ // * The width of the product in the units provided. The value must be between 0 (exclusive) and 3000 (inclusive).
329
+ // */
330
+ // productWidth?: Schema$ProductDimension;
331
+ // /**
332
+ // * Shipping rules.
333
+ // */
334
+ // shipping?: Schema$ProductShipping[];
335
+ // /**
336
+ // * Height of the item for shipping.
337
+ // */
338
+ // shippingHeight?: Schema$ProductShippingDimension;
339
+ // /**
340
+ // * Length of the item for shipping.
341
+ // */
342
+ // shippingLength?: Schema$ProductShippingDimension;
343
+ // /**
344
+ // * Weight of the item for shipping.
345
+ // */
346
+ // shippingWeight?: Schema$ProductShippingWeight;
347
+ // /**
348
+ // * Width of the item for shipping.
349
+ // */
350
+ // shippingWidth?: Schema$ProductShippingDimension;
351
+ // /**
352
+ // * List of country codes (ISO 3166-1 alpha-2) to exclude the offer from Shopping Ads destination. Countries from this list are removed from countries configured in MC feed settings.
353
+ // */
354
+ // shoppingAdsExcludedCountries?: string[] | null;
355
+ // /**
356
+ // * Size of the item. Only one value is allowed. For variants with different sizes, insert a separate product for each size with the same `itemGroupId` value (see size definition).
357
+ // */
358
+ // sizes?: string[] | null;
359
+ // /**
360
+ // * System in which the size is specified. Recommended for apparel items.
361
+ // */
362
+ // sizeSystem?: string | null;
363
+ // /**
364
+ // * The cut of the item. Recommended for apparel items.
365
+ // */
366
+ // sizeType?: string | null;
367
+ ];
368
+ export const CHANNEL = 'online';
369
+ export const GMC_PRODUCT_ID_TEMPLATE = '{PRODUCT_ID}_{VARIANT_ID}';
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * from './constants/google.constants.js';
2
+ export * from './constants/product.constants.js';
3
+ export * from './constants/profile.constants.js';
4
+
5
+ export * from './types/product.types.js';
6
+ export * from './types/profile.types.js';
7
+ export * from './types/company.types.js';
8
+
9
+ export * from './utils/gmc.js';
10
+ export * from './utils/profile.js';
11
+ export * from './utils/utils.js';
@@ -0,0 +1,10 @@
1
+ export type CompanyActionProgress = {
2
+ progress: number;
3
+ totalCount: number;
4
+ name: 'shopify' | 'gmc' | 'issues' | 'shopify-collection';
5
+ threadId: string;
6
+ };
7
+
8
+ export type CompanyActivity = CompanyActionProgress & {
9
+ shop: string;
10
+ };
@@ -0,0 +1,97 @@
1
+ import { GmcProductSyncStatus } from '../constants/google.constants.js';
2
+ import { ProductSyncStatus } from '../constants/product.constants.js';
3
+
4
+ export type AppProductStatus = 'active' | 'draft' | 'archived';
5
+
6
+ export type AppProductVariant = {
7
+ id: number;
8
+ title: string;
9
+ price: string;
10
+ sku: string | null;
11
+ image_id: number | null;
12
+ };
13
+
14
+ export type GmcProductId = {
15
+ id: string;
16
+ profileId: string;
17
+ gmcAccount: string;
18
+ uploaded_at: string;
19
+ status?: GmcProductSyncStatus;
20
+ };
21
+
22
+ export type GoogleProductIssue = {
23
+ servability?: 'demoted' | 'disapproved' | 'unaffected' | string;
24
+ description?: string;
25
+ documentation?: string;
26
+ };
27
+
28
+ export type gmcIssue = {
29
+ gmcProductId: string;
30
+ gmcAccount: string;
31
+ updated_at: string;
32
+ issue: GoogleProductIssue[];
33
+ };
34
+
35
+ export type ShopifyCollection = {
36
+ id: number;
37
+ handle: string;
38
+ title: string;
39
+ };
40
+
41
+ export type AppProduct = {
42
+ status: AppProductStatus;
43
+ handle: string;
44
+ title: string;
45
+ category?: string;
46
+ id: number;
47
+ googleCategory?: number;
48
+ image?: string;
49
+ _id: string;
50
+ gmcIssues?: gmcIssue[];
51
+ gmcProductId?: GmcProductId[];
52
+ updated_at: string;
53
+ variants: AppProductVariant[];
54
+ images: {
55
+ src: string;
56
+ id: string;
57
+ }[];
58
+ collections: ShopifyCollection[];
59
+ tags: string[];
60
+ vendor: string;
61
+ product_type: string;
62
+ syncStatus: ProductSyncStatus;
63
+ };
64
+
65
+ export type GetProductsQuery = {
66
+ before?: string;
67
+ after?: string;
68
+ limit?: number;
69
+ handle?: string;
70
+ title?: string;
71
+ id?: number[];
72
+ sortField?: 'title' | 'id';
73
+ sort?: 'asc' | 'desc';
74
+ fields?: string[];
75
+ gmcProductId?: string[] | boolean;
76
+ };
77
+
78
+ export type GetProductsResponse = {
79
+ products: AppProduct[];
80
+ pagination: {
81
+ firstId: string;
82
+ lastId: string;
83
+ hasMore: boolean;
84
+ hasLess: boolean;
85
+ };
86
+ };
87
+
88
+ export type ProductsList = {
89
+ ids: string[];
90
+ };
91
+
92
+ export type UploadProductBulkPayload = {
93
+ collections: string[];
94
+ vendors: string[];
95
+ types: string[];
96
+ tags: string[];
97
+ };
@@ -0,0 +1,73 @@
1
+ import {
2
+ ProductUploadRuleFilters,
3
+ ProductUploadMappings,
4
+ } from '../constants/profile.constants.js';
5
+ import { RuleOperators } from '../constants/profile.constants.js';
6
+
7
+ export type ProductUploadRuleOpratorType = {
8
+ label: string;
9
+ value: string;
10
+ };
11
+
12
+ export type ProductUploadRuleFilterType = {
13
+ label: string;
14
+ operators: ProductUploadRuleOpratorType[];
15
+ value: string;
16
+ disabled?: boolean;
17
+ };
18
+
19
+ export type RuleOperatorsType = typeof RuleOperators;
20
+ export type ValueType = string | string[] | number[] | number | boolean;
21
+ export type OptionTypeItem = { label: string; value: ValueType };
22
+ export type OptionTypeGroup = { title: string; options: OptionTypeItem[] };
23
+ export type OptionType = OptionTypeItem | OptionTypeGroup | string;
24
+ export type ProductUploadMapSource = {
25
+ required: boolean;
26
+ label: string;
27
+ attribute: string;
28
+ type: 'select' | 'multiselect' | 'text' | 'number';
29
+ choises?: OptionType[];
30
+ rules: ProductUploadRuleFilterType['value'][];
31
+ description?: string;
32
+ defaultValue?: ValueType;
33
+ };
34
+
35
+ export type ProductUploadMapping = {
36
+ attribute: MappingFields;
37
+ value: ValueType;
38
+ rules: {
39
+ sections: {
40
+ ruleItems: {
41
+ attribute: (typeof ProductUploadRuleFilters)[number]['value'];
42
+ operator: ProductUploadRuleOpratorType['value'];
43
+ value: string | string[] | number[] | number | boolean;
44
+ id: string;
45
+ }[];
46
+ }[];
47
+ };
48
+ };
49
+
50
+ type MappingFields = (typeof ProductUploadMappings)[number]['attribute'];
51
+
52
+ export type ProductUploadProfile = {
53
+ id: string;
54
+ name: string;
55
+ active?: boolean;
56
+ rules: ProductUploadRules;
57
+ mappings: ProductUploadMapping[];
58
+ };
59
+
60
+ export type ProductUploadRules = {
61
+ sections: ProductUploadRuleSection[];
62
+ };
63
+
64
+ export type ProductUploadRuleSection = {
65
+ ruleItems: ProductUploadRuleItem[];
66
+ };
67
+
68
+ export type ProductUploadRuleItem = {
69
+ id: string;
70
+ attribute: string;
71
+ operator: string;
72
+ value: string | number | string[] | number[] | boolean;
73
+ };
@@ -0,0 +1,69 @@
1
+ import { GMC_PRODUCT_ID_TEMPLATE } from '../constants/profile.constants.js';
2
+
3
+ export function isGmcProductId (productId: string): boolean {
4
+ return new RegExp(
5
+ GMC_PRODUCT_ID_TEMPLATE.replace('{PRODUCT_ID}', '\\d+').replace(
6
+ '{VARIANT_ID}',
7
+ '\\d+'
8
+ )
9
+ ).test(productId);
10
+ }
11
+
12
+ export function GMCProductIdToShopify (productId: string): number {
13
+ const [, , , id] = productId.split(':');
14
+ return Number(getProductIdFromGmcId(id));
15
+ }
16
+
17
+ export function GmcIdParts (gmcProductId: string): {
18
+ channel: string;
19
+ locale: string;
20
+ country: string;
21
+ idPart: string;
22
+ shopifyId: number;
23
+ shopifyVariantId: number;
24
+ } {
25
+ const [channel, locale, country, idPart] = gmcProductId.split(':');
26
+
27
+ if (!channel || !locale || !country || !idPart) {
28
+ throw new Error(`${gmcProductId} is not a valid GMC product id`);
29
+ }
30
+
31
+ return {
32
+ channel,
33
+ locale,
34
+ country,
35
+ idPart,
36
+ ...getShopifyIdsFromGmcIdPart(idPart),
37
+ };
38
+ }
39
+
40
+ export function makeGmcIdPart (
41
+ productId: number | null,
42
+ variantId: number | null
43
+ ): string {
44
+ return GMC_PRODUCT_ID_TEMPLATE.replace(
45
+ '{PRODUCT_ID}',
46
+ String(productId)
47
+ ).replace('{VARIANT_ID}', String(variantId));
48
+ }
49
+
50
+ export function getShopifyIdsFromGmcIdPart (gmcId: string): {
51
+ shopifyId: number;
52
+ shopifyVariantId: number;
53
+ } {
54
+ if (!isGmcProductId(gmcId)) {
55
+ throw new Error(`${gmcId} is not a valid GMC product id part`);
56
+ }
57
+
58
+ const [productId, variantId] = gmcId.split('_');
59
+ return { shopifyId: Number(productId), shopifyVariantId: Number(variantId) };
60
+ }
61
+
62
+ export function getProductIdFromGmcId (gmcId: string): number {
63
+ if (!isGmcProductId(gmcId)) {
64
+ throw new Error(`${gmcId} is not a valid GMC product id part`);
65
+ }
66
+
67
+ const [productId] = gmcId.split('_');
68
+ return Number(productId);
69
+ }