feat: add needed methods to the file module and providers (#12325)

This commit is contained in:
Harminder Virk
2025-04-30 13:17:31 +05:30
committed by GitHub
parent 3539066146
commit ceb504db2c
8 changed files with 169 additions and 6 deletions
+11
View File
@@ -1,3 +1,4 @@
import { Readable } from "stream"
import { FileAccessPermission } from "./common" import { FileAccessPermission } from "./common"
/** /**
@@ -144,4 +145,14 @@ export interface IFileProvider {
getPresignedUploadUrl?( getPresignedUploadUrl?(
fileData: ProviderGetPresignedUploadUrlDTO fileData: ProviderGetPresignedUploadUrlDTO
): Promise<ProviderFileResultDTO> ): Promise<ProviderFileResultDTO>
/**
* Get the file contents as a readable stream.
*/
getAsStream(fileData: ProviderGetFileDTO): Promise<Readable>
/**
* Get the file contents as a Node.js Buffer
*/
getAsBuffer(fileData: ProviderGetFileDTO): Promise<Buffer>
} }
+25
View File
@@ -1,10 +1,17 @@
import { Readable } from "stream"
import { IModuleService } from "../modules-sdk" import { IModuleService } from "../modules-sdk"
import { FileDTO, FilterableFileProps, UploadFileUrlDTO } from "./common" import { FileDTO, FilterableFileProps, UploadFileUrlDTO } from "./common"
import { FindConfig } from "../common" import { FindConfig } from "../common"
import { Context } from "../shared-context" import { Context } from "../shared-context"
import { IFileProvider } from "./provider"
import { CreateFileDTO, GetUploadFileUrlDTO } from "./mutations" import { CreateFileDTO, GetUploadFileUrlDTO } from "./mutations"
export interface IFileModuleService extends IModuleService { export interface IFileModuleService extends IModuleService {
/**
* Returns a reference to the file provider in use
*/
getProvider(): IFileProvider
/** /**
* This method uploads files to the designated file storage system. * This method uploads files to the designated file storage system.
* *
@@ -157,4 +164,22 @@ export interface IFileModuleService extends IModuleService {
config?: FindConfig<FileDTO>, config?: FindConfig<FileDTO>,
sharedContext?: Context sharedContext?: Context
): Promise<[FileDTO[], number]> ): Promise<[FileDTO[], number]>
/**
* Get the file contents as a readable stream.
*
* @example
* const stream = await fileModuleService.getAsStream("file_123")
* writeable.pipe(stream)
*/
getAsStream(id: string, sharedContext?: Context): Promise<Readable>
/**
* Get the file contents as a Node.js Buffer
*
* @example
* const contents = await fileModuleService.getAsBuffer("file_123")
* contents.toString('utf-8')
*/
getAsBuffer(id: string, sharedContext?: Context): Promise<Buffer>
} }
@@ -1,3 +1,4 @@
import type { Readable } from "stream"
import { FileTypes, IFileProvider } from "@medusajs/types" import { FileTypes, IFileProvider } from "@medusajs/types"
/** /**
@@ -174,4 +175,34 @@ export class AbstractFileProviderService implements IFileProvider {
): Promise<string> { ): Promise<string> {
throw Error("getPresignedDownloadUrl must be overridden by the child class") throw Error("getPresignedDownloadUrl must be overridden by the child class")
} }
/**
* Get the file contents as a readable stream.
*
* @example
* class MyFileProviderService extends AbstractFileProviderService {
* // ...
* async getAsStream(file: ProviderDeleteFileDTO): Promise<Readable> {
* this.client.getAsStream(file.fileKey)
* }
* }
*/
getAsStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable> {
throw Error("getAsStream must be overridden by the child class")
}
/**
* Get the file contents as a Node.js Buffer
*
* @example
* class MyFileProviderService extends AbstractFileProviderService {
* // ...
* async getAsBuffer(file: ProviderDeleteFileDTO): Promise<Buffer> {
* this.client.getAsBuffer(file.fileKey)
* }
* }
*/
getAsBuffer(fileData: FileTypes.ProviderGetFileDTO): Promise<Buffer> {
throw Error("getAsBuffer must be overridden by the child class")
}
} }
@@ -1,3 +1,4 @@
import type { Readable } from "stream"
import { import {
Context, Context,
CreateFileDTO, CreateFileDTO,
@@ -28,6 +29,10 @@ export default class FileModuleService implements FileTypes.IFileModuleService {
return joinerConfig return joinerConfig
} }
getProvider() {
return this.fileProviderService_
}
createFiles( createFiles(
data: CreateFileDTO[], data: CreateFileDTO[],
sharedContext?: Context sharedContext?: Context
@@ -154,4 +159,26 @@ export default class FileModuleService implements FileTypes.IFileModuleService {
1, 1,
] ]
} }
/**
* Get the file contents as a readable stream.
*
* @example
* const stream = await fileModuleService.getAsStream("file_123")
* writeable.pipe(stream)
*/
getAsStream(id: string): Promise<Readable> {
return this.fileProviderService_.getAsStream({ fileKey: id })
}
/**
* Get the file contents as a Node.js Buffer
*
* @example
* const contents = await fileModuleService.getAsBuffer("file_123")
* contents.toString('utf-8')
*/
getAsBuffer(id: string): Promise<Buffer> {
return this.fileProviderService_.getAsBuffer({ fileKey: id })
}
} }
@@ -1,3 +1,4 @@
import type { Readable } from "stream"
import { Constructor, FileTypes } from "@medusajs/framework/types" import { Constructor, FileTypes } from "@medusajs/framework/types"
import { MedusaError } from "@medusajs/framework/utils" import { MedusaError } from "@medusajs/framework/utils"
import { FileProviderRegistrationPrefix } from "@types" import { FileProviderRegistrationPrefix } from "@types"
@@ -68,4 +69,12 @@ export default class FileProviderService {
return this.fileProvider_.getPresignedUploadUrl(fileData) return this.fileProvider_.getPresignedUploadUrl(fileData)
} }
getAsStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable> {
return this.fileProvider_.getAsStream(fileData)
}
getAsBuffer(fileData: FileTypes.ProviderGetFileDTO): Promise<Buffer> {
return this.fileProvider_.getAsBuffer(fileData)
}
} }
@@ -1,5 +1,6 @@
import { ModuleProvider, Modules } from "@medusajs/framework/utils" import { ModuleProvider, Modules } from "@medusajs/framework/utils"
import { LocalFileService } from "./services/local-file" import { LocalFileService } from "./services/local-file"
export { LocalFileService }
const services = [LocalFileService] const services = [LocalFileService]
@@ -3,8 +3,10 @@ import {
AbstractFileProviderService, AbstractFileProviderService,
MedusaError, MedusaError,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { createReadStream } from "fs"
import fs from "fs/promises" import fs from "fs/promises"
import path from "path" import path from "path"
import type { Readable } from "stream"
export class LocalFileService extends AbstractFileProviderService { export class LocalFileService extends AbstractFileProviderService {
static identifier = "localfs" static identifier = "localfs"
@@ -83,6 +85,24 @@ export class LocalFileService extends AbstractFileProviderService {
return return
} }
async getAsStream(file: FileTypes.ProviderGetFileDTO): Promise<Readable> {
const baseDir = file.fileKey.startsWith("private-")
? this.privateUploadDir_
: this.uploadDir_
const filePath = this.getUploadFilePath(baseDir, file.fileKey)
return createReadStream(filePath)
}
async getAsBuffer(file: FileTypes.ProviderGetFileDTO): Promise<Buffer> {
const baseDir = file.fileKey.startsWith("private-")
? this.privateUploadDir_
: this.uploadDir_
const filePath = this.getUploadFilePath(baseDir, file.fileKey)
return fs.readFile(filePath)
}
// The local file provider doesn't support presigned URLs for private files (i.e files not placed in /static). // The local file provider doesn't support presigned URLs for private files (i.e files not placed in /static).
async getPresignedDownloadUrl( async getPresignedDownloadUrl(
file: FileTypes.ProviderGetFileDTO file: FileTypes.ProviderGetFileDTO
@@ -17,6 +17,7 @@ import {
MedusaError, MedusaError,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import path from "path" import path from "path"
import { Readable } from "stream"
import { ulid } from "ulid" import { ulid } from "ulid"
type InjectedDependencies = { type InjectedDependencies = {
@@ -215,4 +216,42 @@ export class S3FileService extends AbstractFileProviderService {
key: fileKey, key: fileKey,
} }
} }
async getAsStream(file: FileTypes.ProviderGetFileDTO): Promise<Readable> {
if (!file?.filename) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`No filename provided`
)
}
const fileKey = `${this.config_.prefix}${file.filename}`
const response = await this.client_.send(
new GetObjectCommand({
Key: fileKey,
Bucket: this.config_.bucket,
})
)
return response.Body! as Readable
}
async getAsBuffer(file: FileTypes.ProviderGetFileDTO): Promise<Buffer> {
if (!file?.filename) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`No filename provided`
)
}
const fileKey = `${this.config_.prefix}${file.filename}`
const response = await this.client_.send(
new GetObjectCommand({
Key: fileKey,
Bucket: this.config_.bucket,
})
)
return Buffer.from(await response.Body!.transformToByteArray())
}
} }