feat: Add support for loading file providers to file module (#7016)

This commit is contained in:
Stevche Radevski
2024-04-09 10:03:56 +02:00
committed by GitHub
parent c03216efbf
commit fbc2959b37
9 changed files with 249 additions and 8 deletions

View File

@@ -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,
})
}

View File

@@ -1,5 +1,7 @@
import { ModuleExports } from "@medusajs/types"
import { FileModuleService } from "@services"
import loadProviders from "./loaders/providers"
export const runMigrations = () => {
return Promise.resolve()
}
@@ -8,7 +10,7 @@ export const revertMigration = () => {
}
const service = FileModuleService
const loaders = [] as any
const loaders = [loadProviders] as any
export const moduleDefinition: ModuleExports = {
service,

View File

@@ -6,12 +6,16 @@ import {
} from "@medusajs/types"
import { joinerConfig } from "../joiner-config"
import FileProviderService from "./file-provider-service"
type InjectedDependencies = {
fileProviderService: FileProviderService
}
export default class FileModuleService {
constructor() {
// @ts-ignore
// eslint-disable-next-line prefer-rest-params
super(...arguments)
protected readonly fileProviderService_: FileProviderService
constructor({ fileProviderService }: InjectedDependencies) {
this.fileProviderService_ = fileProviderService
}
__joinerConfig(): ModuleJoinerConfig {
@@ -25,18 +29,37 @@ export default class FileModuleService {
data: CreateFileDTO[] | CreateFileDTO
): Promise<FileDTO[] | FileDTO> {
const input = Array.isArray(data) ? data : [data]
const files = []
return Array.isArray(data) ? files : files[0]
const files = await Promise.all(
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(id: string, sharedContext?: Context): 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
}
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,
}
}
}

View File

@@ -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)
}
}

View File

@@ -1 +1,2 @@
export { default as FileModuleService } from "./file-module-service"
export { default as FileProviderService } from "./file-provider-service"

View File

@@ -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>
}
}
}

View File

@@ -1,3 +1,4 @@
export * from "./common"
export * from "./mutations"
export * from "./service"
export * from "./provider"

View File

@@ -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 files details, you can access the files 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 doesnt 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>
}