diff --git a/.changeset/wet-seas-lie.md b/.changeset/wet-seas-lie.md new file mode 100644 index 0000000000..74ce95b0a8 --- /dev/null +++ b/.changeset/wet-seas-lie.md @@ -0,0 +1,7 @@ +--- +"@medusajs/notification": patch +"@medusajs/types": patch +"@medusajs/utils": patch +--- + +chore: Inject sandbox handle in cloud config diff --git a/packages/core/types/src/common/config-module.ts b/packages/core/types/src/common/config-module.ts index 9c87aa2bd5..f9d7616d91 100644 --- a/packages/core/types/src/common/config-module.ts +++ b/packages/core/types/src/common/config-module.ts @@ -231,6 +231,10 @@ export type MedusaCloudOptions = { * The endpoint of the Medusa Cloud email service. */ emailsEndpoint?: string + /** + * The sandbox handle of the Medusa Cloud sandbox. + */ + sandboxHandle?: string } /** @@ -890,7 +894,7 @@ export type ProjectConfigOptions = { * This property holds configurations for running in Medusa Cloud. * It gets automatically populated in the cloud, and is not needed outside of it. */ - medusaCloudOptions?: MedusaCloudOptions + cloud?: MedusaCloudOptions } /** diff --git a/packages/core/utils/src/common/__tests__/define-config.spec.ts b/packages/core/utils/src/common/__tests__/define-config.spec.ts index dbb9f24237..e66ab087e1 100644 --- a/packages/core/utils/src/common/__tests__/define-config.spec.ts +++ b/packages/core/utils/src/common/__tests__/define-config.spec.ts @@ -2091,6 +2091,7 @@ describe("defineConfig", function () { "api_key": "test-api-key", "endpoint": "test-emails-endpoint", "environment_handle": "test-environment", + "sandbox_handle": undefined, }, "providers": [ { @@ -2160,6 +2161,12 @@ describe("defineConfig", function () { }, ], "projectConfig": { + "cloud": { + "apiKey": "test-api-key", + "emailsEndpoint": "test-emails-endpoint", + "environmentHandle": "test-environment", + "sandboxHandle": undefined, + }, "databaseUrl": "postgres://localhost/medusa-starter-default", "http": { "adminCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", @@ -2176,10 +2183,186 @@ describe("defineConfig", function () { }, "storeCors": "http://localhost:8000", }, - "medusaCloudOptions": { + "redisOptions": { + "retryStrategy": [Function], + }, + "sessionOptions": {}, + }, + } + `) + }) + + it("should add cloud options to the project config and relevant modules if the environment varianbles is set for a sandbox", function () { + const originalEnv = { ...process.env } + process.env.MEDUSA_CLOUD_SANDBOX_HANDLE = "test-sandbox" + process.env.MEDUSA_CLOUD_API_KEY = "test-api-key" + process.env.MEDUSA_CLOUD_EMAILS_ENDPOINT = "test-emails-endpoint" + const config = defineConfig() + process.env = { ...originalEnv } + + expect(config).toMatchInlineSnapshot(` + { + "admin": { + "backendUrl": "/", + "path": "/app", + }, + "featureFlags": {}, + "logger": undefined, + "modules": { + "api_key": { + "resolve": "@medusajs/medusa/api-key", + }, + "auth": { + "options": { + "providers": [ + { + "id": "emailpass", + "resolve": "@medusajs/medusa/auth-emailpass", + }, + ], + }, + "resolve": "@medusajs/medusa/auth", + }, + "cache": { + "resolve": "@medusajs/medusa/cache-inmemory", + }, + "cart": { + "resolve": "@medusajs/medusa/cart", + }, + "currency": { + "resolve": "@medusajs/medusa/currency", + }, + "customer": { + "resolve": "@medusajs/medusa/customer", + }, + "event_bus": { + "resolve": "@medusajs/medusa/event-bus-local", + }, + "file": { + "options": { + "providers": [ + { + "id": "local", + "resolve": "@medusajs/medusa/file-local", + }, + ], + }, + "resolve": "@medusajs/medusa/file", + }, + "fulfillment": { + "options": { + "providers": [ + { + "id": "manual", + "resolve": "@medusajs/medusa/fulfillment-manual", + }, + ], + }, + "resolve": "@medusajs/medusa/fulfillment", + }, + "inventory": { + "resolve": "@medusajs/medusa/inventory", + }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, + "notification": { + "options": { + "cloud": { + "api_key": "test-api-key", + "endpoint": "test-emails-endpoint", + "environment_handle": undefined, + "sandbox_handle": "test-sandbox", + }, + "providers": [ + { + "id": "local", + "options": { + "channels": [ + "feed", + ], + "name": "Local Notification Provider", + }, + "resolve": "@medusajs/medusa/notification-local", + }, + ], + }, + "resolve": "@medusajs/medusa/notification", + }, + "order": { + "resolve": "@medusajs/medusa/order", + }, + "payment": { + "resolve": "@medusajs/medusa/payment", + }, + "pricing": { + "resolve": "@medusajs/medusa/pricing", + }, + "product": { + "resolve": "@medusajs/medusa/product", + }, + "promotion": { + "resolve": "@medusajs/medusa/promotion", + }, + "region": { + "resolve": "@medusajs/medusa/region", + }, + "sales_channel": { + "resolve": "@medusajs/medusa/sales-channel", + }, + "settings": { + "resolve": "@medusajs/medusa/settings", + }, + "stock_location": { + "resolve": "@medusajs/medusa/stock-location", + }, + "store": { + "resolve": "@medusajs/medusa/store", + }, + "tax": { + "resolve": "@medusajs/medusa/tax", + }, + "user": { + "options": { + "jwt_options": undefined, + "jwt_public_key": undefined, + "jwt_secret": "supersecret", + "jwt_verify_options": undefined, + }, + "resolve": "@medusajs/medusa/user", + }, + "workflows": { + "resolve": "@medusajs/medusa/workflow-engine-inmemory", + }, + }, + "plugins": [ + { + "options": {}, + "resolve": "@medusajs/draft-order", + }, + ], + "projectConfig": { + "cloud": { "apiKey": "test-api-key", "emailsEndpoint": "test-emails-endpoint", - "environmentHandle": "test-environment", + "environmentHandle": undefined, + "sandboxHandle": "test-sandbox", + }, + "databaseUrl": "postgres://localhost/medusa-starter-default", + "http": { + "adminCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", + "authCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", + "cookieSecret": "supersecret", + "jwtPublicKey": undefined, + "jwtSecret": "supersecret", + "restrictedFields": { + "store": [ + ${DEFAULT_STORE_RESTRICTED_FIELDS.map((v) => `"${v}"`).join( + ",\n " + )}, + ], + }, + "storeCors": "http://localhost:8000", }, "redisOptions": { "retryStrategy": [Function], @@ -2190,7 +2373,7 @@ describe("defineConfig", function () { `) }) - it("should merge custom projectConfig.medusaCloudOptions", function () { + it("should merge custom projectConfig.cloud", function () { const originalEnv = { ...process.env } process.env.MEDUSA_CLOUD_ENVIRONMENT_HANDLE = "test-environment" process.env.MEDUSA_CLOUD_API_KEY = "test-api-key" @@ -2198,7 +2381,7 @@ describe("defineConfig", function () { const config = defineConfig({ projectConfig: { http: {} as any, - medusaCloudOptions: { + cloud: { environmentHandle: "overriden-environment", apiKey: "overriden-api-key", emailsEndpoint: "overriden-emails-endpoint", @@ -2279,6 +2462,7 @@ describe("defineConfig", function () { "api_key": "overriden-api-key", "endpoint": "overriden-emails-endpoint", "environment_handle": "overriden-environment", + "sandbox_handle": undefined, }, "providers": [ { @@ -2348,6 +2532,12 @@ describe("defineConfig", function () { }, ], "projectConfig": { + "cloud": { + "apiKey": "overriden-api-key", + "emailsEndpoint": "overriden-emails-endpoint", + "environmentHandle": "overriden-environment", + "sandboxHandle": undefined, + }, "databaseUrl": "postgres://localhost/medusa-starter-default", "http": { "adminCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", @@ -2364,11 +2554,6 @@ describe("defineConfig", function () { }, "storeCors": "http://localhost:8000", }, - "medusaCloudOptions": { - "apiKey": "overriden-api-key", - "emailsEndpoint": "overriden-emails-endpoint", - "environmentHandle": "overriden-environment", - }, "redisOptions": { "retryStrategy": [Function], }, diff --git a/packages/core/utils/src/common/define-config.ts b/packages/core/utils/src/common/define-config.ts index c67ab9f98b..919d7f1f16 100644 --- a/packages/core/utils/src/common/define-config.ts +++ b/packages/core/utils/src/common/define-config.ts @@ -50,7 +50,7 @@ export function defineConfig(config: InputConfig = {}): ConfigModule { const projectConfig = normalizeProjectConfig(config.projectConfig, options) const adminConfig = normalizeAdminConfig(config.admin) const modules = resolveModules(config.modules, options, config.projectConfig) - applyCloudOptionsToModules(modules, projectConfig?.medusaCloudOptions) + applyCloudOptionsToModules(modules, projectConfig?.cloud) const plugins = resolvePlugins(config.plugins, options) return { @@ -369,15 +369,16 @@ function normalizeProjectConfig( http, redisOptions, sessionOptions, - medusaCloudOptions, + cloud, ...restOfProjectConfig } = projectConfig || {} const mergedCloudOptions: MedusaCloudOptions = { environmentHandle: process.env.MEDUSA_CLOUD_ENVIRONMENT_HANDLE, + sandboxHandle: process.env.MEDUSA_CLOUD_SANDBOX_HANDLE, apiKey: process.env.MEDUSA_CLOUD_API_KEY, emailsEndpoint: process.env.MEDUSA_CLOUD_EMAILS_ENDPOINT, - ...medusaCloudOptions, + ...cloud, } const hasCloudOptions = Object.values(mergedCloudOptions).some( (value) => value !== undefined @@ -444,7 +445,7 @@ function normalizeProjectConfig( ...sessionOptions, }, // If there are no cloud options, we better don't pollute the project config for people not using the cloud - ...(hasCloudOptions ? { medusaCloudOptions: mergedCloudOptions } : {}), + ...(hasCloudOptions ? { cloud: mergedCloudOptions } : {}), ...restOfProjectConfig, } satisfies ConfigModule["projectConfig"] @@ -486,6 +487,7 @@ function applyCloudOptionsToModules( api_key: config.apiKey, endpoint: config.emailsEndpoint, environment_handle: config.environmentHandle, + sandbox_handle: config.sandboxHandle, }, ...(module.options ?? {}), } diff --git a/packages/modules/notification/src/loaders/providers.ts b/packages/modules/notification/src/loaders/providers.ts index 70252cc4cd..025416569c 100644 --- a/packages/modules/notification/src/loaders/providers.ts +++ b/packages/modules/notification/src/loaders/providers.ts @@ -15,6 +15,21 @@ import { } from "@types" import { MedusaCloudEmailNotificationProvider } from "../providers/medusa-cloud-email" +const validateCloudOptions = (options: NotificationModuleOptions["cloud"]) => { + const { api_key, endpoint, environment_handle, sandbox_handle } = + options ?? {} + + if (!environment_handle && !sandbox_handle) { + return false + } + + if (!api_key || !endpoint) { + return false + } + + return true +} + const registrationFn = async (klass, container, pluginOptions) => { container.register({ [NotificationProviderRegistrationPrefix + pluginOptions.id]: asFunction( @@ -48,8 +63,11 @@ export default async ({ provider.options?.channels?.some((channel) => channel === "email") ) if (!hasEmailProvider) { - const { api_key, endpoint, environment_handle } = options?.cloud ?? {} - if (api_key && endpoint && environment_handle) { + const shouldRegisterMedusaCloudEmailProvider = validateCloudOptions( + options?.cloud + ) + + if (shouldRegisterMedusaCloudEmailProvider) { await registrationFn(MedusaCloudEmailNotificationProvider, container, { options: options?.cloud, id: "cloud", diff --git a/packages/modules/notification/src/providers/medusa-cloud-email.ts b/packages/modules/notification/src/providers/medusa-cloud-email.ts index 39cebcc776..75df0ba67f 100644 --- a/packages/modules/notification/src/providers/medusa-cloud-email.ts +++ b/packages/modules/notification/src/providers/medusa-cloud-email.ts @@ -16,14 +16,23 @@ export class MedusaCloudEmailNotificationProvider extends AbstractNotificationPr async send( notification: NotificationTypes.ProviderSendNotificationDTO ): Promise { + const headers = { + "Content-Type": "application/json", + Authorization: `Basic ${this.options_.api_key}`, + } + + if (this.options_.sandbox_handle) { + headers["x-medusa-sandbox-handle"] = this.options_.sandbox_handle + } + + if (this.options_.environment_handle) { + headers["x-medusa-environment-handle"] = this.options_.environment_handle + } + try { const response = await fetch(`${this.options_.endpoint}/send`, { method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Basic ${this.options_.api_key}`, - "x-medusa-environment-handle": this.options_.environment_handle, - }, + headers, body: JSON.stringify({ to: notification.to, from: notification.from, diff --git a/packages/modules/notification/src/types/index.ts b/packages/modules/notification/src/types/index.ts index 649e172894..a527e3b1d1 100644 --- a/packages/modules/notification/src/types/index.ts +++ b/packages/modules/notification/src/types/index.ts @@ -42,5 +42,6 @@ export type NotificationModuleOptions = export type MedusaCloudEmailOptions = { api_key: string endpoint: string - environment_handle: string + environment_handle?: string + sandbox_handle?: string }