feat: Add local file provider and wire everything up in the file module (#7134)
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { FileTypes } from "@medusajs/types"
|
||||
import { AbstractFileProviderService } from "@medusajs/utils"
|
||||
|
||||
export class FileProviderServiceFixtures extends AbstractFileProviderService {
|
||||
static identifier = "fixtures-file-provider"
|
||||
protected storage = {}
|
||||
async upload(
|
||||
file: FileTypes.ProviderUploadFileDTO
|
||||
): Promise<FileTypes.ProviderFileResultDTO> {
|
||||
this.storage[file.filename] = file.content
|
||||
return {
|
||||
url: file.filename,
|
||||
key: file.filename,
|
||||
}
|
||||
}
|
||||
async delete(file: FileTypes.ProviderDeleteFileDTO): Promise<void> {
|
||||
delete this.storage[file.fileKey]
|
||||
return
|
||||
}
|
||||
|
||||
async getPresignedDownloadUrl(
|
||||
fileData: FileTypes.ProviderGetFileDTO
|
||||
): Promise<string> {
|
||||
if (this.storage[fileData.fileKey]) {
|
||||
return this.storage[fileData.fileKey]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
export const services = [FileProviderServiceFixtures]
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./default-provider"
|
||||
@@ -1,5 +1,56 @@
|
||||
import { resolve } from "path"
|
||||
import { Modules } from "@medusajs/utils"
|
||||
import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
|
||||
import { Entity, PrimaryKey } from "@mikro-orm/core"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
describe("File Module Service", () => {
|
||||
it("noop", function () {})
|
||||
// The test runner throws if a model is not passed, so we create a dummy entity
|
||||
@Entity({ tableName: "dummy_file_entity" })
|
||||
export default class DummyEntity {
|
||||
@PrimaryKey()
|
||||
id: string
|
||||
}
|
||||
|
||||
const moduleOptions = {
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: {
|
||||
"default-provider": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.FILE,
|
||||
moduleOptions: moduleOptions,
|
||||
moduleModels: [DummyEntity],
|
||||
// TODO: Fix the type of service, it complains for some reason if we pass IFileModuleService
|
||||
testSuite: ({ service }: SuiteOptions<any>) => {
|
||||
describe("File Module Service", () => {
|
||||
it("creates and gets a file", async () => {
|
||||
const res = await service.create({
|
||||
filename: "test.jpg",
|
||||
mimeType: "image/jpeg",
|
||||
content: Buffer.from("test"),
|
||||
})
|
||||
|
||||
expect(res).toEqual({
|
||||
id: "test.jpg",
|
||||
url: "test.jpg",
|
||||
})
|
||||
|
||||
// The fake provider returns the file content as the url
|
||||
const downloadUrl = await service.retrieve("test.jpg")
|
||||
expect(await new Response(downloadUrl.url).text()).toEqual("test")
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import {
|
||||
moduleDefinition,
|
||||
revertMigration,
|
||||
runMigrations,
|
||||
} from "./module-definition"
|
||||
|
||||
export default moduleDefinition
|
||||
export { revertMigration, runMigrations }
|
||||
|
||||
import { moduleDefinition } from "./module-definition"
|
||||
import { initializeFactory, Modules } from "@medusajs/modules-sdk"
|
||||
export * from "./types"
|
||||
export * from "./services"
|
||||
|
||||
export const initialize = initializeFactory({
|
||||
moduleName: Modules.FILE,
|
||||
moduleDefinition,
|
||||
})
|
||||
|
||||
export const runMigrations = moduleDefinition.runMigrations
|
||||
export const revertMigration = moduleDefinition.revertMigration
|
||||
export default moduleDefinition
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { FileProviderService } from "@services"
|
||||
import { FileProviderIdentifierRegistrationName } from "@types"
|
||||
import {
|
||||
FileProviderIdentifierRegistrationName,
|
||||
FileProviderRegistrationPrefix,
|
||||
} from "@types"
|
||||
import { Lifetime, asFunction, asValue } from "awilix"
|
||||
|
||||
const registrationFn = async (klass, container, pluginOptions) => {
|
||||
@@ -9,9 +12,12 @@ const registrationFn = async (klass, container, pluginOptions) => {
|
||||
const key = FileProviderService.getRegistrationIdentifier(klass, name)
|
||||
|
||||
container.register({
|
||||
["file_" + key]: asFunction((cradle) => new klass(cradle, config), {
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
}),
|
||||
[FileProviderRegistrationPrefix + key]: asFunction(
|
||||
(cradle) => new klass(cradle, config),
|
||||
{
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
}
|
||||
),
|
||||
})
|
||||
|
||||
container.registerAdd(FileProviderIdentifierRegistrationName, asValue(key))
|
||||
@@ -25,16 +31,11 @@ export default async ({
|
||||
(
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
) & { provider: ModuleProvider }
|
||||
) & { providers: ModuleProvider[] }
|
||||
>): Promise<void> => {
|
||||
container.registerAdd(
|
||||
FileProviderIdentifierRegistrationName,
|
||||
asValue(undefined)
|
||||
)
|
||||
|
||||
await moduleProviderLoader({
|
||||
container,
|
||||
providers: options?.provider ? [options?.provider] : [],
|
||||
providers: options?.providers || [],
|
||||
registerServiceFn: registrationFn,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
import { FileModuleService } from "@services"
|
||||
import loadProviders from "./loaders/providers"
|
||||
import * as ModuleServices from "@services"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
export const runMigrations = () => {
|
||||
return Promise.resolve()
|
||||
@@ -9,9 +11,15 @@ export const revertMigration = () => {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const service = FileModuleService
|
||||
const loaders = [loadProviders] as any
|
||||
const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
|
||||
moduleModels: {},
|
||||
moduleRepositories: {},
|
||||
moduleServices: ModuleServices,
|
||||
})
|
||||
|
||||
const loaders = [containerLoader, loadProviders] as any
|
||||
|
||||
const service = FileModuleService
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
|
||||
@@ -3,16 +3,20 @@ import {
|
||||
CreateFileDTO,
|
||||
FileDTO,
|
||||
ModuleJoinerConfig,
|
||||
FileTypes,
|
||||
FilterableFileProps,
|
||||
FindConfig,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { joinerConfig } from "../joiner-config"
|
||||
import FileProviderService from "./file-provider-service"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
fileProviderService: FileProviderService
|
||||
}
|
||||
|
||||
export default class FileModuleService {
|
||||
export default class FileModuleService implements FileTypes.IFileModuleService {
|
||||
protected readonly fileProviderService_: FileProviderService
|
||||
constructor({ fileProviderService }: InjectedDependencies) {
|
||||
this.fileProviderService_ = fileProviderService
|
||||
@@ -51,7 +55,6 @@ export default class FileModuleService {
|
||||
return
|
||||
}
|
||||
|
||||
async retrieve(id: string): Promise<FileDTO>
|
||||
async retrieve(id: string): Promise<FileDTO> {
|
||||
const res = await this.fileProviderService_.getPresignedDownloadUrl({
|
||||
fileKey: id,
|
||||
@@ -62,4 +65,65 @@ export default class FileModuleService {
|
||||
url: res,
|
||||
}
|
||||
}
|
||||
|
||||
async list(
|
||||
filters?: FilterableFileProps,
|
||||
config?: FindConfig<FileDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<FileDTO[]> {
|
||||
const id = Array.isArray(filters?.id) ? filters?.id?.[0] : filters?.id
|
||||
if (!id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Listing of files is only supported when filtering by ID."
|
||||
)
|
||||
}
|
||||
|
||||
const res = await this.fileProviderService_.getPresignedDownloadUrl({
|
||||
fileKey: id,
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id,
|
||||
url: res,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
filters?: FilterableFileProps,
|
||||
config?: FindConfig<FileDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[FileDTO[], number]> {
|
||||
const id = Array.isArray(filters?.id) ? filters?.id?.[0] : filters?.id
|
||||
if (!id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Listing and counting of files is only supported when filtering by ID."
|
||||
)
|
||||
}
|
||||
|
||||
const res = await this.fileProviderService_.getPresignedDownloadUrl({
|
||||
fileKey: id,
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return [[], 0]
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
{
|
||||
id,
|
||||
url: res,
|
||||
},
|
||||
],
|
||||
1,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { Constructor, DAL, FileTypes } from "@medusajs/types"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { FileProviderRegistrationPrefix } from "@types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
[key: `file_${string}`]: FileTypes.IFileProvider
|
||||
[
|
||||
key: `${typeof FileProviderRegistrationPrefix}${string}`
|
||||
]: FileTypes.IFileProvider
|
||||
}
|
||||
|
||||
export default class FileProviderService {
|
||||
protected readonly fileProvider_: FileTypes.IFileProvider
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
if (Object.keys(container).length !== 1) {
|
||||
const fileProviderKeys = Object.keys(container).filter((k) =>
|
||||
k.startsWith(FileProviderRegistrationPrefix)
|
||||
)
|
||||
|
||||
if (fileProviderKeys.length !== 1) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`File module should only be initialized with one provider`
|
||||
`File module should be initialized with exactly one provider`
|
||||
)
|
||||
}
|
||||
|
||||
this.fileProvider_ = Object.values(container)[0]
|
||||
this.fileProvider_ = container[fileProviderKeys[0]]
|
||||
}
|
||||
|
||||
static getRegistrationIdentifier(
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
export const FileProviderIdentifierRegistrationName =
|
||||
"file_providers_identifier"
|
||||
|
||||
export const FileProviderRegistrationPrefix = "fs_"
|
||||
|
||||
export type FileModuleOptions = Partial<ModuleServiceInitializeOptions> & {
|
||||
/**
|
||||
* Providers to be registered
|
||||
|
||||
Reference in New Issue
Block a user