@haus-tech/product-import-export-plugin 2.2.0-dev.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 (52) hide show
  1. package/dist/api/plugin.controller.d.ts +8 -0
  2. package/dist/api/plugin.controller.js +49 -0
  3. package/dist/api/product-export.controller.d.ts +14 -0
  4. package/dist/api/product-export.controller.js +117 -0
  5. package/dist/api/product-import.controller.d.ts +11 -0
  6. package/dist/api/product-import.controller.js +62 -0
  7. package/dist/constants.d.ts +2 -0
  8. package/dist/constants.js +5 -0
  9. package/dist/helpers/generate-asset-hash.d.ts +2 -0
  10. package/dist/helpers/generate-asset-hash.js +15 -0
  11. package/dist/helpers/normalize-string.d.ts +1 -0
  12. package/dist/helpers/normalize-string.js +16 -0
  13. package/dist/helpers/remove-update-at-fields.d.ts +5 -0
  14. package/dist/helpers/remove-update-at-fields.js +19 -0
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +17 -0
  17. package/dist/product-import-export.plugin.d.ts +13 -0
  18. package/dist/product-import-export.plugin.js +153 -0
  19. package/dist/providers/import-providers/asset-importer.d.ts +27 -0
  20. package/dist/providers/import-providers/asset-importer.js +127 -0
  21. package/dist/providers/import-providers/import-parser.d.ts +137 -0
  22. package/dist/providers/import-providers/import-parser.js +600 -0
  23. package/dist/providers/import-providers/product-importer.d.ts +47 -0
  24. package/dist/providers/import-providers/product-importer.js +729 -0
  25. package/dist/services/extended-fast-importer.service.d.ts +41 -0
  26. package/dist/services/extended-fast-importer.service.js +418 -0
  27. package/dist/services/product-export.service.d.ts +23 -0
  28. package/dist/services/product-export.service.js +340 -0
  29. package/dist/services/product-import.service.d.ts +21 -0
  30. package/dist/services/product-import.service.js +83 -0
  31. package/dist/types.d.ts +40 -0
  32. package/dist/types.js +2 -0
  33. package/dist/ui/components/export-dialog.component.d.ts +43 -0
  34. package/dist/ui/components/export-dialog.component.html +78 -0
  35. package/dist/ui/components/export-dialog.component.js +108 -0
  36. package/dist/ui/components/export-dialog.component.ts +142 -0
  37. package/dist/ui/components/product-import.component.d.ts +55 -0
  38. package/dist/ui/components/product-import.component.html +76 -0
  39. package/dist/ui/components/product-import.component.js +398 -0
  40. package/dist/ui/components/product-import.component.ts +528 -0
  41. package/dist/ui/product-export.service.d.ts +14 -0
  42. package/dist/ui/product-export.service.js +107 -0
  43. package/dist/ui/product-export.service.ts +107 -0
  44. package/dist/ui/providers.d.ts +2 -0
  45. package/dist/ui/providers.js +49 -0
  46. package/dist/ui/providers.ts +57 -0
  47. package/dist/ui/routes.d.ts +29 -0
  48. package/dist/ui/routes.js +12 -0
  49. package/dist/ui/routes.ts +11 -0
  50. package/dist/ui/translations/en.json +44 -0
  51. package/dist/ui/translations/sv.json +44 -0
  52. package/package.json +30 -0
@@ -0,0 +1,528 @@
1
+ import { SharedModule, getServerLocation } from '@vendure/admin-ui/core'
2
+ import { Component, ViewChild, ElementRef, ChangeDetectorRef, OnInit } from '@angular/core'
3
+ import { NotificationService, LocalStorageService } from '@vendure/admin-ui/core'
4
+ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
5
+ import { TranslateService } from '@ngx-translate/core'
6
+ import { endsWith, startsWith, size, uniq } from 'lodash'
7
+ import { AdminUiConfig } from '@vendure/common/lib/shared-types'
8
+ import { Channel, LanguageCode } from '@vendure/core'
9
+
10
+ type ImportOptions = {
11
+ updateProductSlug?: string
12
+ }
13
+
14
+ type ValidateReturnType = {
15
+ isValid: boolean
16
+ langCodes?: (string | undefined)[]
17
+ clearFile?: boolean
18
+ }
19
+ export interface PluginInitOptions {
20
+ visibleOptions?: Array<keyof ImportOptions>
21
+ defaultOptions?: ImportOptions
22
+ }
23
+
24
+ @Component({
25
+ selector: 'product-import',
26
+ templateUrl: './product-import.component.html',
27
+ standalone: true,
28
+ imports: [SharedModule],
29
+ })
30
+ export class ProductImportComponent implements OnInit {
31
+ serverPath: string
32
+ validateFile: File | null = null
33
+ selectedFile: File | null = null
34
+ updateProductSlug = true
35
+ selectedMainLanguage: LanguageCode | undefined
36
+ availableLanguages: LanguageCode[] = []
37
+
38
+ @ViewChild('fileUpload', { static: false }) fileUploadElement: ElementRef<HTMLInputElement>
39
+
40
+ config: PluginInitOptions
41
+ appConfig: AdminUiConfig
42
+ currentChannel: Channel
43
+
44
+ constructor(
45
+ private notificationService: NotificationService,
46
+ private cd: ChangeDetectorRef,
47
+ private translate: TranslateService,
48
+ private localStorageService: LocalStorageService,
49
+ ) {
50
+ this.serverPath = getServerLocation()
51
+ }
52
+
53
+ async ngOnInit() {
54
+ this.appConfig = await fetch('./vendure-ui-config.json').then((res) => res.json())
55
+
56
+ this.config = await fetch(`${this.serverPath}/product-import-export/config`, {
57
+ method: 'GET',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ },
61
+ })
62
+ .then((res) => res.json())
63
+ .then((data) => data.importOptions)
64
+ }
65
+
66
+ doValidation(file: File): void {
67
+ this.validateCsvStructure(file).then(({ isValid, langCodes, clearFile = true }) => {
68
+ if (isValid) {
69
+ this.selectedFile = file
70
+ } else if (clearFile) {
71
+ this.clearFile()
72
+ }
73
+
74
+ if (langCodes && !this.selectedMainLanguage) {
75
+ this.availableLanguages = langCodes.filter((code) => code) as LanguageCode[]
76
+ this.selectedMainLanguage =
77
+ size(this.availableLanguages) === 1
78
+ ? (this.availableLanguages?.[0] as LanguageCode)
79
+ : undefined
80
+ }
81
+
82
+ this.cd.detectChanges()
83
+ })
84
+ }
85
+
86
+ onMainLanguageChange() {
87
+ this.cd.detectChanges()
88
+
89
+ if (this.validateFile) {
90
+ this.doValidation(this.validateFile)
91
+ }
92
+ }
93
+
94
+ onFileSelected(event: Event): void {
95
+ const input = event.target as HTMLInputElement
96
+ if (input.files && input.files.length > 0) {
97
+ const file = input.files[0]
98
+ if (file.type !== 'text/csv') {
99
+ this.notificationService.error(
100
+ this.translate.instant('product-import.notifications.invalid-file-type'),
101
+ )
102
+ this.clearFile()
103
+ return
104
+ }
105
+
106
+ this.validateFile = file
107
+
108
+ this.doValidation(file)
109
+ }
110
+ }
111
+
112
+ clearFile(): void {
113
+ this.fileUploadElement.nativeElement.value = ''
114
+ this.selectedFile = null
115
+ this.validateFile = null
116
+ this.selectedMainLanguage = undefined
117
+ }
118
+
119
+ async validateCsvStructure(file: File): Promise<ValidateReturnType> {
120
+ const baseColumns = ['sku']
121
+
122
+ const optionalColumns = [
123
+ 'assets',
124
+ 'price',
125
+ 'taxCategory',
126
+ 'stockOnHand',
127
+ 'trackInventory',
128
+ 'variantAssets',
129
+ ]
130
+
131
+ const translatableBaseColumns = ['name']
132
+
133
+ const translatableColumns = [
134
+ 'slug',
135
+ 'description',
136
+ 'facets',
137
+ 'optionGroups',
138
+ 'optionValues',
139
+ 'variantFacets',
140
+ ]
141
+
142
+ return new Promise<ValidateReturnType>((resolve) => {
143
+ const reader = new FileReader()
144
+ reader.onload = async (e) => {
145
+ const text = e.target?.result as string
146
+ const lines = text.split('\n')
147
+
148
+ // Remove empty lines
149
+ lines.forEach((line, index) => {
150
+ if (line.trim() === '') {
151
+ lines.splice(index, 1)
152
+ }
153
+ })
154
+ if (lines.length > 0) {
155
+ const header = this.splitLines(lines[0]).map((col) => col.trim())
156
+ console.log('CSV Header:', header)
157
+
158
+ this.currentChannel = await fetch(`${this.serverPath}/product-import-export/channel`, {
159
+ method: 'GET',
160
+ headers: {
161
+ 'Content-Type': 'application/json',
162
+ ...this.getRequestHeaders(),
163
+ },
164
+ })
165
+ .then((res) => res.json())
166
+ .then((data) => data)
167
+ const availableLanguages = this.currentChannel.availableLanguageCodes
168
+
169
+ // Extract all language codes from headers
170
+ const languageCodes = header
171
+ .map((col) => col.match(/:(\w{2})$/)?.[1])
172
+ .filter((code, index, self) => code && self.indexOf(code) === index)
173
+
174
+ console.log('Language Codes:', languageCodes)
175
+
176
+ let validationLanguage = size(languageCodes) === 0 ? undefined : this.selectedMainLanguage
177
+
178
+ if (!this.selectedMainLanguage && size(languageCodes) < 2) {
179
+ validationLanguage = (languageCodes as LanguageCode[])?.[0]
180
+ }
181
+
182
+ if (!this.selectedMainLanguage && size(languageCodes) > 1) {
183
+ console.log(
184
+ 'Multiple languages found in the CSV file. Please select the main language.',
185
+ )
186
+ resolve({ isValid: true, langCodes: languageCodes, clearFile: false })
187
+ return
188
+ }
189
+
190
+ if (!this.selectedMainLanguage && size(languageCodes) === 0) {
191
+ resolve({ isValid: true, langCodes: availableLanguages, clearFile: false })
192
+ return
193
+ }
194
+
195
+ console.log('Validation language:', validationLanguage)
196
+
197
+ // Check if csv-file includes languages that are not available in the system
198
+
199
+ const missingLanguages = (languageCodes as LanguageCode[]).filter(
200
+ (code) => !availableLanguages.includes(code),
201
+ )
202
+ if (missingLanguages.length > 0) {
203
+ const missingLangs = missingLanguages.join(', ')
204
+ console.log('Missing languages:', missingLangs)
205
+ this.notificationService.error(
206
+ `${this.translate.instant(
207
+ 'product-import.notifications.missing-languages',
208
+ )} ${missingLangs}`,
209
+ )
210
+ resolve({ isValid: false })
211
+ return
212
+ }
213
+
214
+ // Validate base columns
215
+ const missingBaseColumns = baseColumns.filter((col) => !header.includes(col))
216
+ const missingTranslatableBaseColumns = translatableBaseColumns.filter(
217
+ (col) => !header.includes(validationLanguage ? `${col}:${validationLanguage}` : col),
218
+ )
219
+
220
+ const combinedMissingBaseColumns = [
221
+ ...missingBaseColumns,
222
+ ...missingTranslatableBaseColumns,
223
+ ]
224
+ if (combinedMissingBaseColumns.length > 0) {
225
+ const missingColumns = combinedMissingBaseColumns.join(', ')
226
+ console.log('Missing base columns:', missingColumns)
227
+ this.notificationService.error(
228
+ `${this.translate.instant(
229
+ 'product-import.notifications.missing-base-columns',
230
+ )} ${missingColumns}`,
231
+ )
232
+ resolve({ isValid: false })
233
+ return
234
+ }
235
+
236
+ const nameColumn = `name:${validationLanguage}` || 'name'
237
+
238
+ // Get optionGroups and optionValues columns for the main language
239
+ const optionGroupsColumn = `optionGroups:${validationLanguage}` || 'optionGroups'
240
+ const optionValuesColumn = `optionValues:${validationLanguage}` || 'optionValues'
241
+
242
+ const optionGroupsIndex = header.indexOf(optionGroupsColumn)
243
+ const optionValuesIndex = header.indexOf(optionValuesColumn)
244
+
245
+ // if (optionGroupsIndex === -1 || optionValuesIndex === -1) {
246
+ // console.log(
247
+ // `Missing optionGroups or optionValues columns for language: ${validationLanguage}`,
248
+ // )
249
+ // this.notificationService.error(
250
+ // `${this.translate.instant(
251
+ // 'product-import.notifications.missing-option-columns',
252
+ // )} ${validationLanguage}`,
253
+ // )
254
+ // resolve({ isValid: false })
255
+ // return
256
+ // }
257
+
258
+ // Loop through each row to validate the option values
259
+ let currentOptionGroupCount = 0
260
+
261
+ for (let i = 1; i < lines.length; i++) {
262
+ const row = this.splitLines(lines[i]).map((col) => col.trim())
263
+
264
+ // Check if it's a product row (it has a 'name' field)
265
+ const isProductRow = row[header.indexOf(nameColumn)]
266
+
267
+ if (isProductRow) {
268
+ // It's a new product row, get the option groups
269
+ const optionGroups = (row[optionGroupsIndex]?.split('|') ?? []).filter(
270
+ (group) => group !== '',
271
+ )
272
+ const optionValues = (row[optionValuesIndex]?.split('|') ?? []).filter(
273
+ (value) => value !== '',
274
+ )
275
+ currentOptionGroupCount = optionGroups.length
276
+
277
+ // Validate that the number of option values matches the number of option groups
278
+ if (optionValues.length !== currentOptionGroupCount) {
279
+ this.notificationService.error(
280
+ `${this.translate.instant(
281
+ 'product-import.notifications.mismatched-option-values',
282
+ )} Row: ${i + 1}. Expected ${currentOptionGroupCount} option values but got ${optionValues.length}.`,
283
+ )
284
+ resolve({ isValid: false })
285
+ return
286
+ }
287
+ } else {
288
+ // It's a variant row, validate the option values
289
+ const optionValues = (row[optionValuesIndex]?.split('|') ?? []).filter(
290
+ (value) => value !== '',
291
+ )
292
+
293
+ // Validate that the number of option values matches the product's option groups
294
+ if (optionValues.length !== currentOptionGroupCount) {
295
+ this.notificationService.error(
296
+ `${this.translate.instant(
297
+ 'product-import.notifications.mismatched-option-values',
298
+ )} Row: ${i + 1}. Expected ${currentOptionGroupCount} option values but got ${optionValues.length}. ${JSON.stringify(
299
+ optionValues,
300
+ )}`,
301
+ )
302
+ resolve({ isValid: false })
303
+ return
304
+ }
305
+ }
306
+ }
307
+
308
+ console.log('All option values for the main language are valid.')
309
+
310
+ // Check if a row contains JSON, if so check if it is valid. This should be done for all columns, not just translatable ones.
311
+ for (let i = 1; i < lines.length; i++) {
312
+ const row = this.splitLines(lines[i]).map((col) => col.trim())
313
+ for (const col of header) {
314
+ const colIndex = header.indexOf(col)
315
+ if (colIndex !== -1 && row[colIndex]) {
316
+ try {
317
+ const { isJson } = this.checkifJson(row[colIndex])
318
+ if (isJson) {
319
+ JSON.parse(
320
+ row[colIndex]
321
+ .trim()
322
+ .replace(/^"/, '')
323
+ .replace(/"$/, '')
324
+ .replace(/'/g, '"')
325
+ .replace(/\s+/g, ' ')
326
+ .replace(/,\s*'/g, ", '"),
327
+ )
328
+ }
329
+ } catch (error) {
330
+ console.log(`Invalid JSON in column: ${col}, row: ${i + 1}`)
331
+ this.notificationService.error(
332
+ `${this.translate.instant(
333
+ 'product-import.notifications.invalid-json',
334
+ )} ${col}, row: ${i + 1}`,
335
+ )
336
+ resolve({ isValid: false })
337
+ return
338
+ }
339
+ }
340
+ }
341
+ }
342
+ console.log('All JSON values are valid.')
343
+
344
+ const { errorRows: missingBaseTranslatableValuesRows } = this.checkTranslatabeColumns(
345
+ lines,
346
+ translatableBaseColumns,
347
+ header,
348
+ languageCodes,
349
+ )
350
+
351
+ if (missingBaseTranslatableValuesRows.length > 0) {
352
+ console.log('Missing translation values in rows:', missingBaseTranslatableValuesRows)
353
+ this.notificationService.error(
354
+ `${this.translate.instant(
355
+ 'product-import.notifications.missing-main-language-name',
356
+ )} Rows: ${uniq(missingBaseTranslatableValuesRows).join(', ')}`,
357
+ )
358
+
359
+ resolve({ isValid: false })
360
+ return
361
+ }
362
+
363
+ // Check if all translatable columns have values for the main language
364
+ const { errorRows, errorColumns } = this.checkTranslatabeColumns(
365
+ lines,
366
+ translatableColumns,
367
+ header,
368
+ languageCodes,
369
+ )
370
+
371
+ if (errorRows.length > 0) {
372
+ console.log('Missing translation values in rows:', errorRows)
373
+ this.notificationService.warning(
374
+ `${this.translate.instant(
375
+ 'product-import.notifications.missing-translation-values',
376
+ )} Rows: ${uniq(errorRows).join(', ')} Columns: ${uniq(errorColumns).join(', ')}`,
377
+ )
378
+ }
379
+
380
+ resolve({ isValid: true, langCodes: languageCodes })
381
+ } else {
382
+ console.log('No lines in CSV file.')
383
+ this.notificationService.error(
384
+ this.translate.instant('product-import.notifications.no-lines-in-file'),
385
+ )
386
+ resolve({ isValid: false })
387
+ }
388
+ }
389
+ reader.onerror = () => {
390
+ console.log('Error reading file.')
391
+ this.notificationService.error(
392
+ this.translate.instant('product-import.notifications.file-read-error'),
393
+ )
394
+ resolve({ isValid: false })
395
+ }
396
+ reader.readAsText(file)
397
+ })
398
+ }
399
+
400
+ async uploadFile(): Promise<void> {
401
+ if (this.selectedFile && this.selectedMainLanguage) {
402
+ const formData = new FormData()
403
+ formData.append('file', this.selectedFile)
404
+ formData.append('updateProductSlug', this.updateProductSlug.toString())
405
+ formData.append('mainLanguage', this.selectedMainLanguage)
406
+
407
+ try {
408
+ const res = await fetch(`${this.serverPath}/product-import/upload`, {
409
+ method: 'POST',
410
+ body: formData,
411
+ headers: this.getRequestHeaders(),
412
+ })
413
+
414
+ if (res.ok) {
415
+ this.clearFile()
416
+ this.notificationService.success(
417
+ this.translate.instant('product-import.notifications.upload-success'),
418
+ )
419
+ this.cd.detectChanges()
420
+ } else {
421
+ this.notificationService.error(
422
+ this.translate.instant('product-import.notifications.upload-error'),
423
+ )
424
+ }
425
+ } catch (error) {
426
+ this.notificationService.error(
427
+ this.translate.instant('product-import.notifications.upload-error'),
428
+ )
429
+ }
430
+ } else {
431
+ this.notificationService.error(
432
+ this.translate.instant('product-import.notifications.no-file-selected'),
433
+ )
434
+ }
435
+ }
436
+
437
+ splitLines(text: string): string[] {
438
+ // Split text by commas, but not if comma is inside double quotes
439
+ const splittedText = text.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/)
440
+
441
+ return splittedText
442
+ }
443
+
444
+ checkifJson(text: string): {
445
+ startsWithJson: boolean
446
+ endsWithJson: boolean
447
+ startChar: string
448
+ endChar: string
449
+ isJson?: boolean
450
+ } {
451
+ // Replace starting and ending double quotes if exists
452
+ text = text.trim().replace(/^"/, '').replace(/"$/, '')
453
+
454
+ return {
455
+ startsWithJson: startsWith(text.trim(), '{') || startsWith(text.trim(), '['),
456
+ endsWithJson: endsWith(text.trim(), '}') || endsWith(text.trim(), ']'),
457
+ startChar: text.trim().charAt(0),
458
+ endChar: text.trim().charAt(text.trim().length - 1),
459
+ isJson:
460
+ startsWith(text.trim(), '{') ||
461
+ startsWith(text.trim(), '[') ||
462
+ endsWith(text.trim(), '}') ||
463
+ endsWith(text.trim(), ']'),
464
+ }
465
+ }
466
+
467
+ getRequestHeaders(): Record<string, string> {
468
+ const headers: Record<string, string> = {}
469
+ const channelToken = this.localStorageService.get('activeChannelToken')
470
+ const { tokenMethod, channelTokenKey } = this.appConfig
471
+
472
+ if (channelToken) {
473
+ headers[channelTokenKey ?? 'vendure-token'] = channelToken
474
+ }
475
+ if (tokenMethod === 'bearer') {
476
+ const authToken = this.localStorageService.get('authToken')
477
+ if (authToken) {
478
+ headers.authorization = `Bearer ${authToken}`
479
+ }
480
+ }
481
+ return headers
482
+ }
483
+
484
+ checkTranslatabeColumns(
485
+ lines: string[],
486
+ columns: string[],
487
+ header: string[],
488
+ languageCodes: (string | undefined)[],
489
+ ): { errorRows: number[]; errorColumns: string[] } {
490
+ let errorRows: number[] = []
491
+ let errorColumns: string[] = []
492
+
493
+ if (size(languageCodes) < 2) {
494
+ console.log('Only one language found in the file.')
495
+ return { errorRows, errorColumns }
496
+ }
497
+
498
+ for (let i = 1; i < lines.length; i++) {
499
+ const row = this.splitLines(lines[i]).map((col) => col.trim())
500
+ for (const baseColumn of columns) {
501
+ const mainLanguageColumn = `${baseColumn}:${this.selectedMainLanguage}`
502
+ const colIndex = header.indexOf(mainLanguageColumn)
503
+ const mainLanguageValue = row[colIndex]
504
+ const mainLanguageHasValue = mainLanguageValue !== undefined && mainLanguageValue !== ''
505
+
506
+ if (mainLanguageHasValue) {
507
+ continue
508
+ }
509
+ for (const lang of languageCodes) {
510
+ if (lang) {
511
+ // Check if lang is defined
512
+ const langSpecificColumn = `${baseColumn}:${lang}`
513
+ const colIndex = header.indexOf(langSpecificColumn)
514
+ const colValue = row[colIndex]
515
+ const colHasValue = colValue !== undefined && colValue !== ''
516
+
517
+ if (!mainLanguageHasValue && colHasValue) {
518
+ errorRows.push(i + 1)
519
+ errorColumns.push(langSpecificColumn)
520
+ }
521
+ }
522
+ }
523
+ }
524
+ }
525
+
526
+ return { errorRows, errorColumns }
527
+ }
528
+ }
@@ -0,0 +1,14 @@
1
+ import { DataService, LocalStorageService, NotificationService } from '@vendure/admin-ui/core';
2
+ import { Product } from '@vendure/core';
3
+ export declare class ProductExportService {
4
+ protected dataService: DataService;
5
+ private notificationService;
6
+ private localStorageService;
7
+ serverPath: string;
8
+ constructor(dataService: DataService, notificationService: NotificationService, localStorageService: LocalStorageService);
9
+ getCustomFields(productIds: string[]): Promise<string[]>;
10
+ getConfig(): Promise<any>;
11
+ exportProducts(selection: Product[], fileName?: string, customFields?: string[], exportAssetsAs?: 'url' | 'json', selectedExportFields?: string[]): Promise<void>;
12
+ private getHeaders;
13
+ private downloadBlob;
14
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ProductExportService = void 0;
13
+ const core_1 = require("@angular/core");
14
+ const core_2 = require("@vendure/admin-ui/core");
15
+ let ProductExportService = class ProductExportService {
16
+ dataService;
17
+ notificationService;
18
+ localStorageService;
19
+ serverPath;
20
+ constructor(dataService, notificationService, localStorageService) {
21
+ this.dataService = dataService;
22
+ this.notificationService = notificationService;
23
+ this.localStorageService = localStorageService;
24
+ this.serverPath = (0, core_2.getServerLocation)();
25
+ }
26
+ async getCustomFields(productIds) {
27
+ return fetch(`${this.serverPath}/product-export/custom-fields`, {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ ...this.getHeaders(),
32
+ },
33
+ body: JSON.stringify(productIds),
34
+ })
35
+ .then((res) => res.json())
36
+ .then((data) => {
37
+ return data.map((field) => field.name);
38
+ });
39
+ }
40
+ async getConfig() {
41
+ return fetch(`${this.serverPath}/product-import-export/config`, {
42
+ method: 'GET',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ ...this.getHeaders(),
46
+ },
47
+ })
48
+ .then((res) => res.json())
49
+ .then((data) => data);
50
+ }
51
+ async exportProducts(selection, fileName, customFields, exportAssetsAs, selectedExportFields) {
52
+ this.notificationService.info(`Exporting ${selection.length} products to ${fileName}.csv`);
53
+ const productIds = selection.map((product) => product.id);
54
+ try {
55
+ const res = await fetch(`${this.serverPath}/product-export/export?fileName=${fileName}&customFields=${customFields}&exportAssetsAs=${exportAssetsAs}&selectedExportFields=${selectedExportFields}`, {
56
+ method: 'POST',
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ ...this.getHeaders(),
60
+ },
61
+ body: JSON.stringify(productIds),
62
+ });
63
+ if (!res.ok) {
64
+ const json = await res.json();
65
+ throw Error(json?.message);
66
+ }
67
+ const header = res.headers.get('Content-Disposition');
68
+ const parts = header.split(';');
69
+ const filename = parts[1].split('=')[1];
70
+ const blob = await res.blob();
71
+ await this.downloadBlob(blob, filename);
72
+ }
73
+ catch (err) {
74
+ console.error(err);
75
+ this.notificationService.error(err.message);
76
+ }
77
+ }
78
+ getHeaders() {
79
+ const headers = {};
80
+ const channelToken = this.localStorageService.get('activeChannelToken');
81
+ if (channelToken) {
82
+ headers['vendure-token'] = channelToken;
83
+ }
84
+ const authToken = this.localStorageService.get('authToken');
85
+ if (authToken) {
86
+ headers.authorization = `Bearer ${authToken}`;
87
+ }
88
+ return headers;
89
+ }
90
+ async downloadBlob(blob, fileName) {
91
+ const blobUrl = window.URL.createObjectURL(blob);
92
+ const a = document.createElement('a');
93
+ document.body.appendChild(a);
94
+ a.setAttribute('hidden', 'true');
95
+ a.href = blobUrl;
96
+ a.download = fileName;
97
+ a.setAttribute('target', '_blank');
98
+ a.click();
99
+ }
100
+ };
101
+ ProductExportService = __decorate([
102
+ (0, core_1.Injectable)(),
103
+ __metadata("design:paramtypes", [core_2.DataService,
104
+ core_2.NotificationService,
105
+ core_2.LocalStorageService])
106
+ ], ProductExportService);
107
+ exports.ProductExportService = ProductExportService;