feat: order export and upload stream (#14243)

* feat: order export

* Merge branch 'develop' of https://github.com/medusajs/medusa into feat/order-export

* normalize status

* rm util

* serialize totals

* test

* lock

* comments

* configurable order list
This commit is contained in:
Carlos R. L. Rodrigues
2025-12-14 12:02:53 +01:00
committed by GitHub
parent e199f1eb01
commit 9366c6d468
31 changed files with 1041 additions and 37 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,119 @@
import { FileSystem } from "@medusajs/utils"
import fs from "fs/promises"
import path from "path"
import { LocalFileService } from "../../src/services/local-file"
jest.setTimeout(10000)
describe("Local File Plugin", () => {
let localService: LocalFileService
const fixtureImagePath =
process.cwd() + "/integration-tests/__fixtures__/catphoto.jpg"
const uploadDir = path.join(
process.cwd(),
"integration-tests/__tests__/uploads"
)
const fileSystem = new FileSystem(uploadDir)
beforeAll(async () => {
localService = new LocalFileService(
{
logger: console as any,
},
{
upload_dir: uploadDir,
backend_url: "http://localhost:9000/static",
}
)
})
afterAll(async () => {
await fileSystem.cleanup()
})
it(`should upload, read, and then delete a public file successfully`, async () => {
const fileContent = await fs.readFile(fixtureImagePath)
const fixtureAsBase64 = fileContent.toString("base64")
const resp = await localService.upload({
filename: "catphoto.jpg",
mimeType: "image/jpeg",
content: fileContent as any,
access: "public",
})
expect(resp).toEqual({
key: expect.stringMatching(/catphoto.*\.jpg/),
url: expect.stringMatching(
/http:\/\/localhost:9000\/static\/.*catphoto.*\.jpg/
),
})
// For local file provider, we can verify the file exists on disk
const fileKey = resp.key
const baseDir = uploadDir
const filePath = path.join(baseDir, fileKey)
const fileOnDisk = await fs.readFile(filePath)
const fileOnDiskAsBase64 = fileOnDisk.toString("base64")
expect(fileOnDiskAsBase64).toEqual(fixtureAsBase64)
const signedUrl = await localService.getPresignedDownloadUrl({
fileKey: resp.key,
})
expect(signedUrl).toEqual(resp.url)
const buffer = await localService.getAsBuffer({ fileKey: resp.key })
expect(buffer).toEqual(fileContent)
await localService.delete({ fileKey: resp.key })
await expect(fs.access(filePath)).rejects.toThrow()
})
it("uploads using stream", async () => {
const fileContent = await fs.readFile(fixtureImagePath)
const { writeStream, promise } = await localService.getUploadStream({
filename: "catphoto-stream.jpg",
mimeType: "image/jpeg",
access: "public",
})
writeStream.write(fileContent)
writeStream.end()
const resp = await promise
expect(resp).toEqual({
key: expect.stringMatching(/catphoto-stream.*\.jpg/),
url: expect.stringMatching(
/http:\/\/localhost:9000\/static\/.*catphoto-stream.*\.jpg/
),
})
const fileKey = resp.key
const filePath = path.join(uploadDir, fileKey)
const fileOnDisk = await fs.readFile(filePath)
expect(fileOnDisk).toEqual(fileContent)
const signedUrl = await localService.getPresignedDownloadUrl({
fileKey: resp.key,
})
expect(signedUrl).toEqual(resp.url)
const buffer = await localService.getAsBuffer({ fileKey: resp.key })
expect(buffer).toEqual(fileContent)
await localService.delete({ fileKey: resp.key })
await expect(fs.access(filePath)).rejects.toThrow()
})
})
@@ -21,6 +21,7 @@
"license": "MIT",
"scripts": {
"test": "../../../../node_modules/.bin/jest --passWithNoTests src",
"test:integration": "../../../../node_modules/.bin/jest --passWithNoTests --forceExit --testPathPattern=\"integration-tests/__tests__/[^/]*\\.spec\\.ts\"",
"build": "yarn run -T rimraf dist && yarn run -T tsc --build ./tsconfig.json",
"watch": "yarn run -T tsc --watch"
},
@@ -3,10 +3,10 @@ import {
AbstractFileProviderService,
MedusaError,
} from "@medusajs/framework/utils"
import { createReadStream } from "fs"
import { createReadStream, createWriteStream } from "fs"
import fs from "fs/promises"
import path from "path"
import type { Readable } from "stream"
import type { Readable, Writable } from "stream"
export class LocalFileService extends AbstractFileProviderService {
static identifier = "localfs"
@@ -78,6 +78,59 @@ export class LocalFileService extends AbstractFileProviderService {
}
}
async getUploadStream(fileData: FileTypes.ProviderUploadStreamDTO): Promise<{
writeStream: Writable
promise: Promise<FileTypes.ProviderFileResultDTO>
url: string
fileKey: string
}> {
if (!fileData.filename) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`No filename provided`
)
}
const parsedFilename = path.parse(fileData.filename)
const baseDir =
fileData.access === "public" ? this.uploadDir_ : this.privateUploadDir_
await this.ensureDirExists(baseDir, parsedFilename.dir)
const fileKey = path.join(
parsedFilename.dir,
// We prepend "private" to the file key so deletions and presigned URLs can know which folder to look into
`${fileData.access === "public" ? "" : "private-"}${Date.now()}-${
parsedFilename.base
}`
)
const filePath = this.getUploadFilePath(baseDir, fileKey)
const fileUrl = this.getUploadFileUrl(fileKey)
const writeStream = createWriteStream(filePath)
const promise = new Promise<FileTypes.ProviderFileResultDTO>(
(resolve, reject) => {
writeStream.on("finish", () => {
resolve({
url: fileUrl,
key: fileKey,
})
})
writeStream.on("error", (err) => {
reject(err)
})
}
)
return {
writeStream,
promise,
url: fileUrl,
fileKey,
}
}
async delete(
files: FileTypes.ProviderDeleteFileDTO | FileTypes.ProviderDeleteFileDTO[]
): Promise<void> {