diff --git a/integration-tests/api/__tests__/batch-jobs/product/import.js b/integration-tests/api/__tests__/batch-jobs/product/import.js index 65e2ea9c4b051..c424237ae1838 100644 --- a/integration-tests/api/__tests__/batch-jobs/product/import.js +++ b/integration-tests/api/__tests__/batch-jobs/product/import.js @@ -111,145 +111,148 @@ describe("Product import batch job", () => { const productsResponse = await api.get("/admin/products", adminReqConfig) expect(productsResponse.data.count).toBe(2) - expect(productsResponse.data.products).toEqual([ - expect.objectContaining({ - id: "O6S1YQ6mKm", - title: "Test product", - description: "test-product-description-1", - handle: "test-product-product-1", - is_giftcard: false, - status: "draft", - thumbnail: "test-image.png", - variants: [ - expect.objectContaining({ - title: "Test variant", - product_id: "O6S1YQ6mKm", - sku: "test-sku-1", - barcode: "test-barcode-1", - ean: null, - upc: null, - inventory_quantity: 10, - prices: [ - expect.objectContaining({ - currency_code: "eur", - amount: 100, - region_id: "region-product-import-0", - }), - expect.objectContaining({ - currency_code: "usd", - amount: 110, - }), - expect.objectContaining({ - currency_code: "dkk", - amount: 130, - region_id: "region-product-import-1", - }), - ], - options: [ - expect.objectContaining({ - value: "option 1 value red", - }), - expect.objectContaining({ - value: "option 2 value 1", - }), - ], - }), - ], - images: [ - expect.objectContaining({ - url: "test-image.png", - }), - ], - options: [ - expect.objectContaining({ - title: "test-option-1", - product_id: "O6S1YQ6mKm", - }), - expect.objectContaining({ - title: "test-option-2", - product_id: "O6S1YQ6mKm", - }), - ], - tags: [ - expect.objectContaining({ - value: "123_1", - }), - ], - }), - expect.objectContaining({ - id: "5VxiEkmnPV", - title: "Test product", - description: "test-product-description", - handle: "test-product-product-2", - is_giftcard: false, - status: "draft", - thumbnail: "test-image.png", - profile_id: expect.any(String), - variants: [ - expect.objectContaining({ - title: "Test variant", - product_id: "5VxiEkmnPV", - sku: "test-sku-2", - barcode: "test-barcode-2", - ean: null, - upc: null, - inventory_quantity: 10, - allow_backorder: false, - manage_inventory: true, - prices: [ - expect.objectContaining({ - currency_code: "dkk", - amount: 110, - region_id: "region-product-import-2", - }), - ], - options: [ - expect.objectContaining({ - value: "Option 1 value 1", - }), - ], - }), - expect.objectContaining({ - title: "Test variant", - product_id: "5VxiEkmnPV", - sku: "test-sku-3", - barcode: "test-barcode-3", - ean: null, - upc: null, - inventory_quantity: 10, - allow_backorder: false, - manage_inventory: true, - prices: [ - expect.objectContaining({ - currency_code: "usd", - amount: 120, - region_id: null, - }), - ], - options: [ - expect.objectContaining({ - value: "Option 1 Value blue", - }), - ], - }), - ], - images: [ - expect.objectContaining({ - url: "test-image.png", - }), - ], - options: [ - expect.objectContaining({ - title: "test-option", - product_id: "5VxiEkmnPV", - }), - ], - tags: [ - expect.objectContaining({ - value: "123", - }), - ], - }), - ]) + expect(productsResponse.data.products).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "O6S1YQ6mKm", + title: "Test product", + description: + "Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\\n100% organic cotton, soft and crisp to the touch. Made in Portugal.", + handle: "test-product-product-1", + is_giftcard: false, + status: "draft", + thumbnail: "test-image.png", + variants: [ + expect.objectContaining({ + title: "Test variant", + product_id: "O6S1YQ6mKm", + sku: "test-sku-1", + barcode: "test-barcode-1", + ean: null, + upc: null, + inventory_quantity: 10, + prices: [ + expect.objectContaining({ + currency_code: "eur", + amount: 100, + region_id: "region-product-import-0", + }), + expect.objectContaining({ + currency_code: "usd", + amount: 110, + }), + expect.objectContaining({ + currency_code: "dkk", + amount: 130, + region_id: "region-product-import-1", + }), + ], + options: [ + expect.objectContaining({ + value: "option 1 value red", + }), + expect.objectContaining({ + value: "option 2 value 1", + }), + ], + }), + ], + images: [ + expect.objectContaining({ + url: "test-image.png", + }), + ], + options: [ + expect.objectContaining({ + title: "test-option-1", + product_id: "O6S1YQ6mKm", + }), + expect.objectContaining({ + title: "test-option-2", + product_id: "O6S1YQ6mKm", + }), + ], + tags: [ + expect.objectContaining({ + value: "123_1", + }), + ], + }), + expect.objectContaining({ + id: "5VxiEkmnPV", + title: "Test product", + description: "test-product-description", + handle: "test-product-product-2", + is_giftcard: false, + status: "draft", + thumbnail: "test-image.png", + profile_id: expect.any(String), + variants: [ + expect.objectContaining({ + title: "Test variant", + product_id: "5VxiEkmnPV", + sku: "test-sku-2", + barcode: "test-barcode-2", + ean: null, + upc: null, + inventory_quantity: 10, + allow_backorder: false, + manage_inventory: true, + prices: [ + expect.objectContaining({ + currency_code: "dkk", + amount: 110, + region_id: "region-product-import-2", + }), + ], + options: [ + expect.objectContaining({ + value: "Option 1 value 1", + }), + ], + }), + expect.objectContaining({ + title: "Test variant", + product_id: "5VxiEkmnPV", + sku: "test-sku-3", + barcode: "test-barcode-3", + ean: null, + upc: null, + inventory_quantity: 10, + allow_backorder: false, + manage_inventory: true, + prices: [ + expect.objectContaining({ + currency_code: "usd", + amount: 120, + region_id: null, + }), + ], + options: [ + expect.objectContaining({ + value: "Option 1 Value blue", + }), + ], + }), + ], + images: [ + expect.objectContaining({ + url: "test-image.png", + }), + ], + options: [ + expect.objectContaining({ + title: "test-option", + product_id: "5VxiEkmnPV", + }), + ], + tags: [ + expect.objectContaining({ + value: "123", + }), + ], + }), + ]) + ) }) }) diff --git a/integration-tests/api/__tests__/batch-jobs/product/product-import.csv b/integration-tests/api/__tests__/batch-jobs/product/product-import.csv index 1ae3f8f006c29..b54958648dc54 100644 --- a/integration-tests/api/__tests__/batch-jobs/product/product-import.csv +++ b/integration-tests/api/__tests__/batch-jobs/product/product-import.csv @@ -1,4 +1,4 @@ Product id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product Mid Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External ID,Product Profile Name,Product Profile Type,Variant id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow backorder,Variant Manage inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant Mid Code,Variant Material,Price ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url -O6S1YQ6mKm,test-product-product-1,Test product,,test-product-description-1,draft,,,,,,,,,,Test collection 1,test-collection1,test-type-1,123_1,TRUE,,profile_1,profile_type_1,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,100,110,130,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png +O6S1YQ6mKm,test-product-product-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,test-type-1,123_1,TRUE,,profile_1,profile_type_1,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,100,110,130,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png 5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,110,test-option,Option 1 value 1,,,test-image.png -5VxiEkmnPV,test-product-product-2,\"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\\n100% organic cotton, soft and crisp to the touch. Made in Portugal.\",,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,120,,,test-option,Option 1 Value blue,,,test-image.png +5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,profile_2,profile_type_2,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,120,,,test-option,Option 1 Value blue,,,test-image.png \ No newline at end of file diff --git a/integration-tests/api/package.json b/integration-tests/api/package.json index 3f64739050ba6..fdd84a2398a3c 100644 --- a/integration-tests/api/package.json +++ b/integration-tests/api/package.json @@ -11,7 +11,8 @@ "@medusajs/medusa": "1.3.5-dev-1661328147668", "faker": "^5.5.3", "medusa-interfaces": "1.3.2-dev-1661328147668", - "typeorm": "^0.2.31" + "typeorm": "^0.2.31", + "mkdirp": "^1.0.4" }, "devDependencies": { "@babel/cli": "^7.12.10", diff --git a/integration-tests/api/src/services/local-file-service.js b/integration-tests/api/src/services/local-file-service.js index 500e4c6e50d7d..1bc25a2013253 100644 --- a/integration-tests/api/src/services/local-file-service.js +++ b/integration-tests/api/src/services/local-file-service.js @@ -1,12 +1,12 @@ import { AbstractFileService } from "@medusajs/medusa" import stream from "stream" +import { resolve } from "path" import * as fs from "fs" -import * as path from "path" +import mkdirp from "mkdirp" export default class LocalFileService extends AbstractFileService { - // eslint-disable-next-line no-empty-pattern constructor({}, options) { - super({}) + super({}, options) this.upload_dir_ = process.env.UPLOAD_DIR ?? options.upload_dir ?? "uploads/images" @@ -15,49 +15,56 @@ export default class LocalFileService extends AbstractFileService { } } - async upload(file) { - const uploadPath = path.join( - this.upload_dir_, - path.dirname(file.originalname) - ) + upload(file) { + return new Promise((resolvePromise, reject) => { + const path = resolve(this.upload_dir_, file.originalname) - if (!fs.existsSync(uploadPath)) { - fs.mkdirSync(uploadPath, { recursive: true }) - } - - const filePath = path.resolve(this.upload_dir_, file.originalname) - fs.writeFile(filePath, "", (error) => { - if (error) { - throw error + let content = "" + if (file.filename) { + content = fs.readFileSync( + resolve(process.cwd(), "uploads", file.filename) + ) } + + const pathSegments = path.split("/") + pathSegments.splice(-1) + const dirname = pathSegments.join("/") + mkdirp.sync(dirname, { recursive: true }) + + fs.writeFile(path, content.toString(), (err) => { + if (err) { + reject(err) + } + + resolvePromise({ url: path }) + }) }) - return { url: filePath } } - async delete({ name }) { - return new Promise((resolve, _) => { - const path = resolve(this.upload_dir_, name) + delete({ fileKey }) { + return new Promise((resolvePromise, reject) => { + const path = resolve(this.upload_dir_, fileKey) fs.unlink(path, (err) => { if (err) { - throw err + reject(err) } - resolve("file unlinked") + resolvePromise("file unlinked") }) }) } async getUploadStreamDescriptor({ name, ext }) { const fileKey = `${name}.${ext}` - const filePath = path.resolve(this.upload_dir_, fileKey) + const path = resolve(this.upload_dir_, fileKey) - const isFileExists = fs.existsSync(filePath) + const isFileExists = fs.existsSync(path) if (!isFileExists) { await this.upload({ originalname: fileKey }) } const pass = new stream.PassThrough() - pass.pipe(fs.createWriteStream(filePath)) + pass.pipe(fs.createWriteStream(path)) return { writeStream: pass, @@ -67,11 +74,23 @@ export default class LocalFileService extends AbstractFileService { } } - async getDownloadStream(fileData) { - const filePath = path.resolve( - this.upload_dir_, - fileData.fileKey + (fileData.ext ? `.${fileData.ext}` : "") - ) - return fs.createReadStream(filePath) + async getDownloadStream({ fileKey }) { + return new Promise((resolvePromise, reject) => { + try { + const path = resolve(this.upload_dir_, fileKey) + const data = fs.readFileSync(path) + const readable = stream.Readable() + readable._read = function () {} + readable.push(data.toString()) + readable.push(null) + resolvePromise(readable) + } catch (e) { + reject(e) + } + }) + } + + async getPresignedDownloadUrl({ fileKey }) { + return `${this.upload_dir_}/${fileKey}` } } diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap b/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap index 6a01f77347eea..767863c603c40 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap @@ -2,7 +2,7 @@ exports[`Product export strategy should process the batch job and generate the appropriate output 1`] = ` Array [ - "Product ID;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External ID;Product Profile Name;Product Profile Type;Variant ID;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow backorder;Variant Manage inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url + "Product id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product Mid Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External ID;Product Profile Name;Product Profile Type;Variant id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow backorder;Variant Manage inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant Mid Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url ", "product-export-strategy-product-1;test-product-product-1;Test product;;\\"test-product-description-1\\\\ntest-product-description-1 second line\\\\ntest-product-description-1 third line\\\\nforth line\\";draft;;;;;;;;;;Test collection 1;test-collection1;test-type-1;123_1;true;;profile_1;profile_type_1;product-export-strategy-variant-1;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;100;110;130;;test-option-1;option 1 value 1;test-option-2;option 2 value 1;test-image.png ", @@ -15,7 +15,7 @@ Array [ exports[`Product export strategy with sales channels should process the batch job and generate the appropriate output 1`] = ` Array [ - "Product ID;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External ID;Product Profile Name;Product Profile Type;Variant ID;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow backorder;Variant Manage inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url;Sales channel 1 Name;Sales channel 1 Description;Sales channel 2 Name;Sales channel 2 Description + "Product id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product Mid Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External ID;Product Profile Name;Product Profile Type;Variant id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow backorder;Variant Manage inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant Mid Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url;Sales channel 1 Name;Sales channel 1 Description;Sales channel 2 Name;Sales channel 2 Description ", "product-export-strategy-product-1;test-product-product-1;Test product;;\\"test-product-description-1\\\\ntest-product-description-1 second line\\\\ntest-product-description-1 third line\\\\nforth line\\";draft;;;;;;;;;;Test collection 1;test-collection1;test-type-1;123_1;true;;profile_1;profile_type_1;product-export-strategy-variant-1;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;100;110;130;;test-option-1;option 1 value 1;test-option-2;option 2 value 1;test-image.png;SC 1;\\"SC 1\\\\nSC 1 second line\\\\nSC 1 third line\\\\nSC 1 forth line\\";; ", diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts index 19eefda57732f..a33226dbca88c 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts @@ -13,7 +13,7 @@ import { FlagRouter } from "../../../../utils/flag-router" import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" const productServiceMock = { - withTransaction: function() { + withTransaction: function () { return this }, list: jest.fn().mockImplementation(() => Promise.resolve(productsToExport)), @@ -51,7 +51,7 @@ describe("Product export strategy", () => { fileKey: "product-export.csv", }) }), - withTransaction: function() { + withTransaction: function () { return this }, } @@ -73,7 +73,7 @@ describe("Product export strategy", () => { } as ProductExportBatchJob const batchJobServiceMock = { - withTransaction: function() { + withTransaction: function () { return this }, update: jest.fn().mockImplementation((jobOrId, data) => { @@ -129,7 +129,7 @@ describe("Product export strategy", () => { ) await productExportStrategy.preProcessBatchJob(fakeJob.id) const template = await productExportStrategy.buildHeader(fakeJob) - expect(template).toMatch(/.*Product ID.*/) + expect(template).toMatch(/.*Product id.*/) expect(template).toMatch(/.*Product Handle.*/) expect(template).toMatch(/.*Product Title.*/) expect(template).toMatch(/.*Product Subtitle.*/) @@ -142,7 +142,7 @@ describe("Product export strategy", () => { expect(template).toMatch(/.*Product Height.*/) expect(template).toMatch(/.*Product HS Code.*/) expect(template).toMatch(/.*Product Origin Country.*/) - expect(template).toMatch(/.*Product MID Code.*/) + expect(template).toMatch(/.*Product Mid Code.*/) expect(template).toMatch(/.*Product Material.*/) expect(template).toMatch(/.*Product Collection Title.*/) expect(template).toMatch(/.*Product Collection Handle.*/) @@ -154,7 +154,7 @@ describe("Product export strategy", () => { expect(template).toMatch(/.*Product Profile Type.*/) expect(template).toMatch(/.*Product Profile Type.*/) - expect(template).toMatch(/.*Variant ID.*/) + expect(template).toMatch(/.*Variant id.*/) expect(template).toMatch(/.*Variant Title.*/) expect(template).toMatch(/.*Variant SKU.*/) expect(template).toMatch(/.*Variant Barcode.*/) @@ -166,7 +166,7 @@ describe("Product export strategy", () => { expect(template).toMatch(/.*Variant Height.*/) expect(template).toMatch(/.*Variant HS Code.*/) expect(template).toMatch(/.*Variant Origin Country.*/) - expect(template).toMatch(/.*Variant MID Code.*/) + expect(template).toMatch(/.*Variant Mid Code.*/) expect(template).toMatch(/.*Variant Material.*/) expect(template).toMatch(/.*Option 1 Name.*/) @@ -314,7 +314,7 @@ describe("Product export strategy with sales channels", () => { fileKey: "product-export.csv", }) }), - withTransaction: function() { + withTransaction: function () { return this }, } @@ -336,7 +336,7 @@ describe("Product export strategy with sales channels", () => { } as ProductExportBatchJob const batchJobServiceMock = { - withTransaction: function() { + withTransaction: function () { return this }, update: jest.fn().mockImplementation((jobOrId, data) => { @@ -394,7 +394,7 @@ describe("Product export strategy with sales channels", () => { ) await productExportStrategy.preProcessBatchJob(fakeJob.id) const template = await productExportStrategy.buildHeader(fakeJob) - expect(template).toMatch(/.*Product ID.*/) + expect(template).toMatch(/.*Product id.*/) expect(template).toMatch(/.*Product Handle.*/) expect(template).toMatch(/.*Product Title.*/) expect(template).toMatch(/.*Product Subtitle.*/) @@ -407,7 +407,7 @@ describe("Product export strategy with sales channels", () => { expect(template).toMatch(/.*Product Height.*/) expect(template).toMatch(/.*Product HS Code.*/) expect(template).toMatch(/.*Product Origin Country.*/) - expect(template).toMatch(/.*Product MID Code.*/) + expect(template).toMatch(/.*Product Mid Code.*/) expect(template).toMatch(/.*Product Material.*/) expect(template).toMatch(/.*Product Collection Title.*/) expect(template).toMatch(/.*Product Collection Handle.*/) @@ -419,7 +419,7 @@ describe("Product export strategy with sales channels", () => { expect(template).toMatch(/.*Product Profile Type.*/) expect(template).toMatch(/.*Product Profile Type.*/) - expect(template).toMatch(/.*Variant ID.*/) + expect(template).toMatch(/.*Variant id.*/) expect(template).toMatch(/.*Variant Title.*/) expect(template).toMatch(/.*Variant SKU.*/) expect(template).toMatch(/.*Variant Barcode.*/) @@ -431,7 +431,7 @@ describe("Product export strategy with sales channels", () => { expect(template).toMatch(/.*Variant Height.*/) expect(template).toMatch(/.*Variant HS Code.*/) expect(template).toMatch(/.*Variant Origin Country.*/) - expect(template).toMatch(/.*Variant MID Code.*/) + expect(template).toMatch(/.*Variant Mid Code.*/) expect(template).toMatch(/.*Variant Material.*/) expect(template).toMatch(/.*Option 1 Name.*/) diff --git a/packages/medusa/src/strategies/batch-jobs/product/import.ts b/packages/medusa/src/strategies/batch-jobs/product/import.ts index 9e2965787d9d5..fd64072057849 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/import.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/import.ts @@ -135,7 +135,10 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { async getImportInstructions( csvData: TParsedProductImportRowData[] ): Promise> { - const shippingProfile = await this.shippingProfileService_.retrieveDefault() + const transactionManager = this.transactionManager_ ?? this.manager_ + const shippingProfile = await this.shippingProfileService_ + .withTransaction(transactionManager) + .retrieveDefault() const seenProducts = {} @@ -224,8 +227,9 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { * @param batchJobId - An id of a job that is being preprocessed. */ async preProcessBatchJob(batchJobId: string): Promise { + const transactionManager = this.transactionManager_ ?? this.manager_ const batchJob = await this.batchJobService_ - .withTransaction(this.transactionManager_) + .withTransaction(transactionManager) .retrieve(batchJobId) const csvFileKey = (batchJob.context as ImportJobContext).fileKey @@ -233,8 +237,16 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { fileKey: csvFileKey, }) - const parsedData = await this.csvParser_.parse(csvStream) - const builtData = await this.csvParser_.buildData(parsedData) + let builtData: Record[] + try { + const parsedData = await this.csvParser_.parse(csvStream) + builtData = await this.csvParser_.buildData(parsedData) + } catch (e) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "The csv file parsing failed due to: " + e.message + ) + } const ops = await this.getImportInstructions(builtData) @@ -248,7 +260,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { }) await this.batchJobService_ - .withTransaction(this.transactionManager_) + .withTransaction(transactionManager) .update(batchJobId, { result: { advancement_count: 0, @@ -537,10 +549,13 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { variantOp, productId: string ): Promise { + const transactionManager = this.transactionManager_ ?? this.manager_ const productOptions = variantOp["variant.options"] || [] + const productServiceTx = + this.productService_.withTransaction(transactionManager) for (const o of productOptions) { - const option = await this.productService_.retrieveOptionByTitle( + const option = await productServiceTx.retrieveOptionByTitle( o._title, productId ) diff --git a/packages/medusa/src/strategies/batch-jobs/product/index.ts b/packages/medusa/src/strategies/batch-jobs/product/index.ts index eea878f27564c..98f7e10d26b0d 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/index.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/index.ts @@ -219,7 +219,7 @@ export const productExportSchemaDescriptors = new Map< }, ], [ - "Variant Id", + "Variant id", { accessor: (variant: ProductVariant): string => variant?.id ?? "", entityName: "variant", diff --git a/packages/medusa/src/strategies/batch-jobs/product/utils.ts b/packages/medusa/src/strategies/batch-jobs/product/utils.ts index 67826d619c1f0..5e4f7459fc06f 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/utils.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/utils.ts @@ -1,10 +1,11 @@ +import { TParsedProductImportRowData } from "./types" +import { csvRevertCellContentFormatter } from "../../../utils" + /** * Pick keys for a new object by regex. * @param data - Initial data object * @param regex - A regex used to pick which keys are going to be copied in the new object */ -import { TParsedProductImportRowData } from "./types" - export function pickObjectPropsByRegex( data: TParsedProductImportRowData, regex: RegExp @@ -14,7 +15,11 @@ export function pickObjectPropsByRegex( for (const k in data) { if (variantKeyPredicate(k)) { - ret[k] = data[k] + const formattedData = + typeof data[k] === "string" + ? csvRevertCellContentFormatter(data[k] as string) + : data[k] + ret[k] = formattedData } } diff --git a/packages/medusa/src/subscribers/batch-job.ts b/packages/medusa/src/subscribers/batch-job.ts index e1b4bb93c3d4a..f362b194b643e 100644 --- a/packages/medusa/src/subscribers/batch-job.ts +++ b/packages/medusa/src/subscribers/batch-job.ts @@ -47,7 +47,7 @@ class BatchJobSubscriber { .preProcessBatchJob(batchJob.id) await batchJobServiceTx.setPreProcessingDone(batchJob.id) } catch (e) { - await batchJobServiceTx.setFailed(batchJob.id) + await this.batchJobService_.setFailed(batchJob.id) throw e } }) @@ -62,13 +62,13 @@ class BatchJobSubscriber { batchJob.type ) - await this.batchJobService_.setProcessing(batchJob.id) + await batchJobServiceTx.setProcessing(batchJob.id) try { await batchJobStrategy.withTransaction(manager).processJob(batchJob.id) await batchJobServiceTx.complete(batchJob.id) } catch (e) { - await batchJobServiceTx.setFailed(batchJob.id) + await this.batchJobService_.setFailed(batchJob.id) throw e } }) diff --git a/packages/medusa/src/utils/csv-cell-content-formatter.ts b/packages/medusa/src/utils/csv-cell-content-formatter.ts index fb698f89e0a9f..a08ec9a552843 100644 --- a/packages/medusa/src/utils/csv-cell-content-formatter.ts +++ b/packages/medusa/src/utils/csv-cell-content-formatter.ts @@ -1,7 +1,7 @@ -const newLineRegexp = new RegExp(/\n/g) -const doubleQuoteRegexp = new RegExp(/"/g) - export function csvCellContentFormatter(str: string): string { + const newLineRegexp = new RegExp(/\n/g) + const doubleQuoteRegexp = new RegExp(/"/g) + const hasNewLineChar = !!str.match(newLineRegexp) if (!hasNewLineChar) { return str @@ -13,3 +13,10 @@ export function csvCellContentFormatter(str: string): string { return `"${formatterStr}"` } + +export function csvRevertCellContentFormatter(str: string): string { + if (str.startsWith('"')) { + str = str.substring(1, str.length - 1) + } + return str +}