feat: Add support for loading file providers to file module (#7016)
This commit is contained in:
@@ -0,0 +1,40 @@
|
|||||||
|
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||||
|
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
|
||||||
|
import { FileProviderService } from "@services"
|
||||||
|
import { FileProviderIdentifierRegistrationName } from "@types"
|
||||||
|
import { Lifetime, asFunction, asValue } from "awilix"
|
||||||
|
|
||||||
|
const registrationFn = async (klass, container, pluginOptions) => {
|
||||||
|
Object.entries(pluginOptions.config || []).map(([name, config]) => {
|
||||||
|
const key = FileProviderService.getRegistrationIdentifier(klass, name)
|
||||||
|
|
||||||
|
container.register({
|
||||||
|
["file_" + key]: asFunction((cradle) => new klass(cradle, config), {
|
||||||
|
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
container.registerAdd(FileProviderIdentifierRegistrationName, asValue(key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async ({
|
||||||
|
container,
|
||||||
|
options,
|
||||||
|
}: LoaderOptions<
|
||||||
|
(
|
||||||
|
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||||
|
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||||
|
) & { provider: ModuleProvider }
|
||||||
|
>): Promise<void> => {
|
||||||
|
container.registerAdd(
|
||||||
|
FileProviderIdentifierRegistrationName,
|
||||||
|
asValue(undefined)
|
||||||
|
)
|
||||||
|
|
||||||
|
await moduleProviderLoader({
|
||||||
|
container,
|
||||||
|
providers: options?.provider ? [options?.provider] : [],
|
||||||
|
registerServiceFn: registrationFn,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { ModuleExports } from "@medusajs/types"
|
import { ModuleExports } from "@medusajs/types"
|
||||||
import { FileModuleService } from "@services"
|
import { FileModuleService } from "@services"
|
||||||
|
import loadProviders from "./loaders/providers"
|
||||||
|
|
||||||
export const runMigrations = () => {
|
export const runMigrations = () => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
@@ -8,7 +10,7 @@ export const revertMigration = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const service = FileModuleService
|
const service = FileModuleService
|
||||||
const loaders = [] as any
|
const loaders = [loadProviders] as any
|
||||||
|
|
||||||
export const moduleDefinition: ModuleExports = {
|
export const moduleDefinition: ModuleExports = {
|
||||||
service,
|
service,
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ import {
|
|||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
|
|
||||||
import { joinerConfig } from "../joiner-config"
|
import { joinerConfig } from "../joiner-config"
|
||||||
|
import FileProviderService from "./file-provider-service"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
fileProviderService: FileProviderService
|
||||||
|
}
|
||||||
|
|
||||||
export default class FileModuleService {
|
export default class FileModuleService {
|
||||||
constructor() {
|
protected readonly fileProviderService_: FileProviderService
|
||||||
// @ts-ignore
|
constructor({ fileProviderService }: InjectedDependencies) {
|
||||||
// eslint-disable-next-line prefer-rest-params
|
this.fileProviderService_ = fileProviderService
|
||||||
super(...arguments)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__joinerConfig(): ModuleJoinerConfig {
|
__joinerConfig(): ModuleJoinerConfig {
|
||||||
@@ -25,18 +29,37 @@ export default class FileModuleService {
|
|||||||
data: CreateFileDTO[] | CreateFileDTO
|
data: CreateFileDTO[] | CreateFileDTO
|
||||||
): Promise<FileDTO[] | FileDTO> {
|
): Promise<FileDTO[] | FileDTO> {
|
||||||
const input = Array.isArray(data) ? data : [data]
|
const input = Array.isArray(data) ? data : [data]
|
||||||
const files = []
|
const files = await Promise.all(
|
||||||
return Array.isArray(data) ? files : files[0]
|
input.map((file) => this.fileProviderService_.upload(file))
|
||||||
|
)
|
||||||
|
const result = files.map((file) => ({
|
||||||
|
id: file.key,
|
||||||
|
url: file.url,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return Array.isArray(data) ? result : result[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(ids: string[], sharedContext?: Context): Promise<void>
|
async delete(ids: string[], sharedContext?: Context): Promise<void>
|
||||||
async delete(id: string, sharedContext?: Context): Promise<void>
|
async delete(id: string, sharedContext?: Context): Promise<void>
|
||||||
async delete(ids: string[] | string): Promise<void> {
|
async delete(ids: string[] | string): Promise<void> {
|
||||||
|
const input = Array.isArray(ids) ? ids : [ids]
|
||||||
|
await Promise.all(
|
||||||
|
input.map((id) => this.fileProviderService_.delete({ fileKey: id }))
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
async retrieve(id: string): Promise<FileDTO>
|
async retrieve(id: string): Promise<FileDTO>
|
||||||
async retrieve(id: string): Promise<FileDTO> {
|
async retrieve(id: string): Promise<FileDTO> {
|
||||||
return {} as FileDTO
|
const res = await this.fileProviderService_.getPresignedDownloadUrl({
|
||||||
|
fileKey: id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
url: res,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Constructor, DAL, FileTypes } from "@medusajs/types"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
[key: `file_${string}`]: FileTypes.IFileProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FileProviderService {
|
||||||
|
protected readonly fileProvider_: FileTypes.IFileProvider
|
||||||
|
|
||||||
|
constructor(container: InjectedDependencies) {
|
||||||
|
if (Object.keys(container).length !== 1) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`File module should only be initialized with one provider`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fileProvider_ = Object.values(container)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
static getRegistrationIdentifier(
|
||||||
|
providerClass: Constructor<FileTypes.IFileProvider>,
|
||||||
|
optionName?: string
|
||||||
|
) {
|
||||||
|
return `${(providerClass as any).identifier}_${optionName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
upload(
|
||||||
|
file: FileTypes.ProviderUploadFileDTO
|
||||||
|
): Promise<FileTypes.ProviderFileResultDTO> {
|
||||||
|
return this.fileProvider_.upload(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(fileData: FileTypes.ProviderDeleteFileDTO): Promise<void> {
|
||||||
|
return this.fileProvider_.delete(fileData)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPresignedDownloadUrl(
|
||||||
|
fileData: FileTypes.ProviderGetFileDTO
|
||||||
|
): Promise<string> {
|
||||||
|
return this.fileProvider_.getPresignedDownloadUrl(fileData)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { default as FileModuleService } from "./file-module-service"
|
export { default as FileModuleService } from "./file-module-service"
|
||||||
|
export { default as FileProviderService } from "./file-provider-service"
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
ModuleProviderExports,
|
||||||
|
ModuleServiceInitializeOptions,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
|
||||||
|
export const FileProviderIdentifierRegistrationName =
|
||||||
|
"file_providers_identifier"
|
||||||
|
|
||||||
|
export type FileModuleOptions = Partial<ModuleServiceInitializeOptions> & {
|
||||||
|
/**
|
||||||
|
* Providers to be registered
|
||||||
|
*/
|
||||||
|
provider?: {
|
||||||
|
/**
|
||||||
|
* The module provider to be registered
|
||||||
|
*/
|
||||||
|
resolve: string | ModuleProviderExports
|
||||||
|
options: {
|
||||||
|
/**
|
||||||
|
* key value pair of the provider name and the configuration to be passed to the provider constructor
|
||||||
|
*/
|
||||||
|
config: Record<string, unknown>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from "./common"
|
export * from "./common"
|
||||||
export * from "./mutations"
|
export * from "./mutations"
|
||||||
export * from "./service"
|
export * from "./service"
|
||||||
|
export * from "./provider"
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* @interface
|
||||||
|
*
|
||||||
|
* Details of a file upload's result.
|
||||||
|
*/
|
||||||
|
export type ProviderFileResultDTO = {
|
||||||
|
/**
|
||||||
|
* The file's URL.
|
||||||
|
*/
|
||||||
|
url: string
|
||||||
|
/**
|
||||||
|
* The file's key. This key is used in other operations,
|
||||||
|
* such as deleting a file.
|
||||||
|
*/
|
||||||
|
key: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface
|
||||||
|
*
|
||||||
|
* The details of a file to retrieve.
|
||||||
|
*/
|
||||||
|
export type ProviderGetFileDTO = {
|
||||||
|
/**
|
||||||
|
* The file's key.
|
||||||
|
*/
|
||||||
|
fileKey: string
|
||||||
|
/**
|
||||||
|
* Whether the file is private.
|
||||||
|
*/
|
||||||
|
isPrivate?: boolean
|
||||||
|
[x: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface
|
||||||
|
*
|
||||||
|
* The details of the file to remove.
|
||||||
|
*/
|
||||||
|
export type ProviderDeleteFileDTO = {
|
||||||
|
/**
|
||||||
|
* The file's key. When uploading a file, the
|
||||||
|
* returned key is used here.
|
||||||
|
*/
|
||||||
|
fileKey: string
|
||||||
|
[x: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface
|
||||||
|
*
|
||||||
|
* The details of the file to create.
|
||||||
|
*/
|
||||||
|
export type ProviderUploadFileDTO = {
|
||||||
|
/**
|
||||||
|
* The filename of the uploaded file
|
||||||
|
*/
|
||||||
|
filename: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mimetype of the uploaded file
|
||||||
|
*/
|
||||||
|
mimeType: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file content
|
||||||
|
*/
|
||||||
|
content: Blob
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ## Overview
|
||||||
|
*
|
||||||
|
* File provider interface for the file module.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface IFileProvider {
|
||||||
|
/**
|
||||||
|
* This method is used to upload a file
|
||||||
|
*
|
||||||
|
* @param {ProviderUploadFileDTO} file - The contents and metadata of the file.
|
||||||
|
* Among the file’s details, you can access the file’s path in the `path` property of the file object.
|
||||||
|
* @returns {Promise<ProviderFileResultDTO>} The details of the upload's result.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
upload(file: ProviderUploadFileDTO): Promise<ProviderFileResultDTO>
|
||||||
|
/**
|
||||||
|
* This method is used to delete a file from storage.
|
||||||
|
*
|
||||||
|
* @param {ProviderDeleteFileDTO} fileData - The details of the file to remove.
|
||||||
|
* @returns {Promise<void>} Resolves when the file is deleted successfully.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
delete(fileData: ProviderDeleteFileDTO): Promise<void>
|
||||||
|
/**
|
||||||
|
* This method is used to retrieve a download URL of the file. For some file services, such as S3, a presigned URL indicates a temporary URL to get access to a file.
|
||||||
|
*
|
||||||
|
* If your file service doesn’t perform or offer a similar functionality, you can just return the URL to download the file.
|
||||||
|
*
|
||||||
|
* @param {ProviderGetFileDTO} fileData - The details of the file.
|
||||||
|
* @returns {Promise<string>} The presigned URL to download the file
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getPresignedDownloadUrl(fileData: ProviderGetFileDTO): Promise<string>
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user