From 6904743f78878023794d77f2c3162ca364c6291c Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Fri, 1 May 2020 15:55:41 +0200 Subject: [PATCH] feat(core): Group api options in VendureConfig Closes #327 BREAKING CHANGE: Options in the VendureConfig related to the API have been moved into a new location: `VendureConfig.apiOptions`. The affected options are `hostname`, `port`, `adminApiPath`, `shopApiPath`, `channelTokenKey`, `cors`, `middleware` and `apolloServerPlugins`. ```TypeScript // before const config: VendureConfig = { port: 3000, middleware: [/*...*/], // ... } // after const config: VendureConfig = { apiOptions: { port: 3000, middleware: [/*...*/], }, // ... } ``` This also applies to the `ConfigService`, in case you are using it in a custom plugin. --- packages/admin-ui-plugin/src/plugin.ts | 13 +- .../e2e/asset-server-plugin.e2e-spec.ts | 4 +- packages/asset-server-plugin/src/plugin.ts | 2 +- .../core/e2e/apollo-server-plugin.e2e-spec.ts | 14 +- packages/core/e2e/plugin.e2e-spec.ts | 8 +- packages/core/src/api/api.module.ts | 4 +- .../src/api/common/request-context.service.ts | 4 +- .../api/config/configure-graphql-module.ts | 2 +- packages/core/src/app.module.ts | 6 +- packages/core/src/bootstrap.ts | 41 ++++-- .../core/src/config/config.service.mock.ts | 20 +-- packages/core/src/config/config.service.ts | 36 +---- packages/core/src/config/default-config.ts | 25 ++-- packages/core/src/config/vendure-config.ts | 132 ++++++++++-------- packages/core/src/plugin/plugin-utils.ts | 15 +- packages/create/templates/vendure-config.hbs | 8 +- packages/dev-server/dev-config.ts | 7 +- .../e2e/elasticsearch-plugin.e2e-spec.ts | 4 +- packages/email-plugin/src/plugin.ts | 12 +- packages/testing/src/config/test-config.ts | 10 +- .../testing/src/create-test-environment.ts | 11 +- .../src/data-population/populate-customers.ts | 2 +- packages/testing/src/simple-graphql-client.ts | 6 +- packages/testing/src/test-server.ts | 6 +- 24 files changed, 215 insertions(+), 177 deletions(-) diff --git a/packages/admin-ui-plugin/src/plugin.ts b/packages/admin-ui-plugin/src/plugin.ts index 40ca627397..d700b196b8 100644 --- a/packages/admin-ui-plugin/src/plugin.ts +++ b/packages/admin-ui-plugin/src/plugin.ts @@ -21,7 +21,7 @@ import fs from 'fs-extra'; import { Server } from 'http'; import path from 'path'; -import { DEFAULT_APP_PATH, defaultAvailableLanguages, defaultLanguage, loggerCtx } from './constants'; +import { defaultAvailableLanguages, defaultLanguage, DEFAULT_APP_PATH, loggerCtx } from './constants'; /** * @description @@ -137,7 +137,7 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose { } else { port = this.options.port; } - config.middleware.push({ + config.apiOptions.middleware.push({ handler: createProxyHandler({ hostname: this.options.hostname, port, @@ -148,7 +148,7 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose { route, }); if (this.isDevModeApp(app)) { - config.middleware.push({ + config.apiOptions.middleware.push({ handler: createProxyHandler({ hostname: this.options.hostname, port, @@ -244,9 +244,12 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose { return partialConfig ? (partialConfig as AdminUiConfig)[prop] || defaultVal : defaultVal; }; return { - adminApiPath: propOrDefault('adminApiPath', this.configService.adminApiPath), + adminApiPath: propOrDefault('adminApiPath', this.configService.apiOptions.adminApiPath), apiHost: propOrDefault('apiHost', AdminUiPlugin.options.apiHost || 'http://localhost'), - apiPort: propOrDefault('apiPort', AdminUiPlugin.options.apiPort || this.configService.port), + apiPort: propOrDefault( + 'apiPort', + AdminUiPlugin.options.apiPort || this.configService.apiOptions.port, + ), tokenMethod: propOrDefault('tokenMethod', authOptions.tokenMethod || 'cookie'), authTokenHeaderKey: propOrDefault( 'authTokenHeaderKey', diff --git a/packages/asset-server-plugin/e2e/asset-server-plugin.e2e-spec.ts b/packages/asset-server-plugin/e2e/asset-server-plugin.e2e-spec.ts index 29993d85cf..d09f791b2b 100644 --- a/packages/asset-server-plugin/e2e/asset-server-plugin.e2e-spec.ts +++ b/packages/asset-server-plugin/e2e/asset-server-plugin.e2e-spec.ts @@ -21,7 +21,9 @@ describe('AssetServerPlugin', () => { const { server, adminClient, shopClient } = createTestEnvironment( mergeConfig(testConfig, { - port: 5050, + apiOptions: { + port: 5050, + }, workerOptions: { options: { port: 5055, diff --git a/packages/asset-server-plugin/src/plugin.ts b/packages/asset-server-plugin/src/plugin.ts index 4991eaf27e..00e4ad95ed 100644 --- a/packages/asset-server-plugin/src/plugin.ts +++ b/packages/asset-server-plugin/src/plugin.ts @@ -155,7 +155,7 @@ export class AssetServerPlugin implements OnVendureBootstrap, OnVendureClose { config.assetOptions.assetStorageStrategy = this.assetStorage; config.assetOptions.assetNamingStrategy = this.options.namingStrategy || new HashedAssetNamingStrategy(); - config.middleware.push({ + config.apiOptions.middleware.push({ handler: createProxyHandler({ ...this.options, label: 'Asset Server' }), route: this.options.route, }); diff --git a/packages/core/e2e/apollo-server-plugin.e2e-spec.ts b/packages/core/e2e/apollo-server-plugin.e2e-spec.ts index f3e70573d5..ce67b652fb 100644 --- a/packages/core/e2e/apollo-server-plugin.e2e-spec.ts +++ b/packages/core/e2e/apollo-server-plugin.e2e-spec.ts @@ -1,3 +1,4 @@ +import { mergeConfig } from '@vendure/core'; import { ApolloServerPlugin, GraphQLRequestContext, @@ -8,7 +9,7 @@ import gql from 'graphql-tag'; import path from 'path'; import { initialData } from '../../../e2e-common/e2e-initial-data'; -import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config'; +import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config'; import { createTestEnvironment } from '../../testing/lib/create-test-environment'; class MyApolloServerPlugin implements ApolloServerPlugin { @@ -38,10 +39,13 @@ class MyApolloServerPlugin implements ApolloServerPlugin { } describe('custom apolloServerPlugins', () => { - const { server, adminClient, shopClient } = createTestEnvironment({ - ...testConfig, - apolloServerPlugins: [new MyApolloServerPlugin()], - }); + const { server, adminClient, shopClient } = createTestEnvironment( + mergeConfig(testConfig, { + apiOptions: { + apolloServerPlugins: [new MyApolloServerPlugin()], + }, + }), + ); beforeAll(async () => { await server.init({ diff --git a/packages/core/e2e/plugin.e2e-spec.ts b/packages/core/e2e/plugin.e2e-spec.ts index 6428a3f1e8..c59cd8de5e 100644 --- a/packages/core/e2e/plugin.e2e-spec.ts +++ b/packages/core/e2e/plugin.e2e-spec.ts @@ -5,7 +5,7 @@ import gql from 'graphql-tag'; import path from 'path'; import { initialData } from '../../../e2e-common/e2e-initial-data'; -import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config'; +import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config'; import { TestPluginWithAllLifecycleHooks } from './fixtures/test-plugins/with-all-lifecycle-hooks'; import { TestAPIExtensionPlugin } from './fixtures/test-plugins/with-api-extensions'; @@ -129,7 +129,7 @@ describe('Plugins', () => { }); describe('REST plugins', () => { - const restControllerUrl = `http://localhost:${testConfig.port}/test`; + const restControllerUrl = `http://localhost:${testConfig.apiOptions.port}/test`; it('public route', async () => { const response = await shopClient.fetch(restControllerUrl + '/public'); @@ -164,7 +164,7 @@ describe('Plugins', () => { describe('processContext', () => { it('server context', async () => { const response = await shopClient.fetch( - `http://localhost:${testConfig.port}/process-context/server`, + `http://localhost:${testConfig.apiOptions.port}/process-context/server`, ); const body = await response.text(); @@ -172,7 +172,7 @@ describe('Plugins', () => { }); it('worker context', async () => { const response = await shopClient.fetch( - `http://localhost:${testConfig.port}/process-context/worker`, + `http://localhost:${testConfig.apiOptions.port}/process-context/worker`, ); const body = await response.text(); diff --git a/packages/core/src/api/api.module.ts b/packages/core/src/api/api.module.ts index 80e899d182..6045460fff 100644 --- a/packages/core/src/api/api.module.ts +++ b/packages/core/src/api/api.module.ts @@ -27,7 +27,7 @@ import { ValidateCustomFieldsInterceptor } from './middleware/validate-custom-fi ShopApiModule, configureGraphQLModule(configService => ({ apiType: 'shop', - apiPath: configService.shopApiPath, + apiPath: configService.apiOptions.shopApiPath, typePaths: ['type', 'shop-api', 'common'].map(p => path.join(__dirname, 'schema', p, '*.graphql'), ), @@ -35,7 +35,7 @@ import { ValidateCustomFieldsInterceptor } from './middleware/validate-custom-fi })), configureGraphQLModule(configService => ({ apiType: 'admin', - apiPath: configService.adminApiPath, + apiPath: configService.apiOptions.adminApiPath, typePaths: ['type', 'admin-api', 'common'].map(p => path.join(__dirname, 'schema', p, '*.graphql'), ), diff --git a/packages/core/src/api/common/request-context.service.ts b/packages/core/src/api/common/request-context.service.ts index 97e9cb338b..5312dbb78d 100644 --- a/packages/core/src/api/common/request-context.service.ts +++ b/packages/core/src/api/common/request-context.service.ts @@ -54,7 +54,7 @@ export class RequestContextService { } private getChannelToken(req: Request): string { - const tokenKey = this.configService.channelTokenKey; + const tokenKey = this.configService.apiOptions.channelTokenKey; let channelToken = ''; if (req && req.query && req.query[tokenKey]) { @@ -86,7 +86,7 @@ export class RequestContextService { return false; } const permissionsOnChannel = user.roles - .filter((role) => role.channels.find((c) => idsAreEqual(c.id, channel.id))) + .filter(role => role.channels.find(c => idsAreEqual(c.id, channel.id))) .reduce((output, role) => [...output, ...role.permissions], [] as Permission[]); return this.arraysIntersect(permissions, permissionsOnChannel); } diff --git a/packages/core/src/api/config/configure-graphql-module.ts b/packages/core/src/api/config/configure-graphql-module.ts index 2894f6b2ef..55c4722650 100644 --- a/packages/core/src/api/config/configure-graphql-module.ts +++ b/packages/core/src/api/config/configure-graphql-module.ts @@ -146,7 +146,7 @@ async function createGraphQLOptions( new IdCodecPlugin(idCodecService), new TranslateErrorsPlugin(i18nService), new AssetInterceptorPlugin(configService), - ...configService.apolloServerPlugins, + ...configService.apiOptions.apolloServerPlugins, ], } as GqlModuleOptions; diff --git a/packages/core/src/app.module.ts b/packages/core/src/app.module.ts index 1539c62774..68f96a77ad 100644 --- a/packages/core/src/app.module.ts +++ b/packages/core/src/app.module.ts @@ -38,7 +38,7 @@ export class AppModule implements NestModule, OnApplicationBootstrap, OnApplicat } configure(consumer: MiddlewareConsumer) { - const { adminApiPath, shopApiPath } = this.configService; + const { adminApiPath, shopApiPath, middleware } = this.configService.apiOptions; const i18nextHandler = this.i18nService.handle(); const defaultMiddleware: Array<{ handler: RequestHandler; route?: string }> = [ { handler: i18nextHandler, route: adminApiPath }, @@ -53,7 +53,7 @@ export class AppModule implements NestModule, OnApplicationBootstrap, OnApplicat defaultMiddleware.push({ handler: cookieHandler, route: adminApiPath }); defaultMiddleware.push({ handler: cookieHandler, route: shopApiPath }); } - const allMiddleware = defaultMiddleware.concat(this.configService.middleware); + const allMiddleware = defaultMiddleware.concat(middleware); const middlewareByRoute = this.groupMiddlewareByRoute(allMiddleware); for (const [route, handlers] of Object.entries(middlewareByRoute)) { consumer.apply(...handlers).forRoutes(route); @@ -76,7 +76,7 @@ export class AppModule implements NestModule, OnApplicationBootstrap, OnApplicat ): { [route: string]: RequestHandler[] } { const result = {} as { [route: string]: RequestHandler[] }; for (const middleware of middlewareArray) { - const route = middleware.route || this.configService.adminApiPath; + const route = middleware.route || this.configService.apiOptions.adminApiPath; if (!result[route]) { result[route] = []; } diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index fa88dadf0f..8433253720 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -43,14 +43,15 @@ export async function bootstrap(userConfig: Partial): Promise, -): Promise> { +): Promise> { if (userConfig) { + checkForDeprecatedOptions(userConfig); setConfig(userConfig); } @@ -207,10 +209,10 @@ export async function getAllEntities(userConfig: Partial): Promis * If the 'bearer' tokenMethod is being used, then we automatically expose the authTokenHeaderKey header * in the CORS options, making sure to preserve any user-configured exposedHeaders. */ -function setExposedHeaders(config: ReadOnlyRequired) { +function setExposedHeaders(config: Readonly) { if (config.authOptions.tokenMethod === 'bearer') { const authTokenHeaderKey = config.authOptions.authTokenHeaderKey as string; - const corsOptions = config.cors; + const corsOptions = config.apiOptions.cors; if (typeof corsOptions !== 'boolean') { const { exposedHeaders } = corsOptions; let exposedHeadersWithAuthKey: string[]; @@ -257,17 +259,18 @@ function workerWelcomeMessage(config: VendureConfig) { Logger.info(`Vendure Worker started${transportString}${connectionString}`); } -function logWelcomeMessage(config: VendureConfig) { +function logWelcomeMessage(config: RuntimeVendureConfig) { let version: string; try { version = require('../package.json').version; } catch (e) { version = ' unknown'; } + const { port, shopApiPath, adminApiPath } = config.apiOptions; Logger.info(`=================================================`); - Logger.info(`Vendure server (v${version}) now running on port ${config.port}`); - Logger.info(`Shop API: http://localhost:${config.port}/${config.shopApiPath}`); - Logger.info(`Admin API: http://localhost:${config.port}/${config.adminApiPath}`); + Logger.info(`Vendure server (v${version}) now running on port ${port}`); + Logger.info(`Shop API: http://localhost:${port}/${shopApiPath}`); + Logger.info(`Admin API: http://localhost:${port}/${adminApiPath}`); logProxyMiddlewares(config); Logger.info(`=================================================`); } @@ -284,3 +287,23 @@ function disableSynchronize(userConfig: ReadOnlyRequired): ReadOn } as ConnectionOptions; return config; } + +function checkForDeprecatedOptions(config: Partial) { + const deprecatedApiOptions = [ + 'hostname', + 'port', + 'adminApiPath', + 'shopApiPath', + 'channelTokenKey', + 'cors', + 'middleware', + 'apolloServerPlugins', + ]; + const deprecatedOptionsUsed = deprecatedApiOptions.filter(option => config.hasOwnProperty(option)); + if (deprecatedOptionsUsed.length) { + throw new Error( + `The following VendureConfig options are deprecated: ${deprecatedOptionsUsed.join(', ')}\n` + + `They have been moved to the "apiOptions" object. Please update your configuration.`, + ); + } +} diff --git a/packages/core/src/config/config.service.mock.ts b/packages/core/src/config/config.service.mock.ts index 6bdb402690..36244a08fa 100644 --- a/packages/core/src/config/config.service.mock.ts +++ b/packages/core/src/config/config.service.mock.ts @@ -5,13 +5,18 @@ import { ConfigService } from './config.service'; import { EntityIdStrategy, PrimaryKeyType } from './entity-id-strategy/entity-id-strategy'; export class MockConfigService implements MockClass { + apiOptions = { + channelTokenKey: 'vendure-token', + adminApiPath: 'admin-api', + shopApiPath: 'shop-api', + port: 3000, + cors: false, + middleware: [], + apolloServerPlugins: [], + }; authOptions: {}; defaultChannelToken: 'channel-token'; - channelTokenKey: 'vendure-token'; - adminApiPath = 'admin-api'; - shopApiPath = 'shop-api'; - port = 3000; - cors = false; + defaultLanguageCode: jest.Mock; roundingStrategy: {}; entityIdStrategy = new MockIdStrategy(); @@ -35,10 +40,9 @@ export class MockConfigService implements MockClass { orderOptions = {}; workerOptions = {}; customFields = {}; - middleware = []; - logger = {} as any; - apolloServerPlugins = []; + plugins = []; + logger = {} as any; jobQueueOptions = {}; } diff --git a/packages/core/src/config/config.service.ts b/packages/core/src/config/config.service.ts index 0a6acc9001..dddd016800 100644 --- a/packages/core/src/config/config.service.ts +++ b/packages/core/src/config/config.service.ts @@ -10,8 +10,10 @@ import { CustomFields } from './custom-field/custom-field-types'; import { EntityIdStrategy } from './entity-id-strategy/entity-id-strategy'; import { Logger, VendureLogger } from './logger/vendure-logger'; import { + ApiOptions, AssetOptions, - AuthOptions, CatalogOptions, + AuthOptions, + CatalogOptions, ImportExportOptions, JobQueueOptions, OrderOptions, @@ -36,6 +38,10 @@ export class ConfigService implements VendureConfig { } } + get apiOptions(): Required { + return this.activeConfig.apiOptions; + } + get authOptions(): Required { return this.activeConfig.authOptions; } @@ -48,30 +54,10 @@ export class ConfigService implements VendureConfig { return this.activeConfig.defaultChannelToken; } - get channelTokenKey(): string { - return this.activeConfig.channelTokenKey; - } - get defaultLanguageCode(): LanguageCode { return this.activeConfig.defaultLanguageCode; } - get adminApiPath(): string { - return this.activeConfig.adminApiPath; - } - - get shopApiPath(): string { - return this.activeConfig.shopApiPath; - } - - get port(): number { - return this.activeConfig.port; - } - - get cors(): boolean | CorsOptions { - return this.activeConfig.cors; - } - get entityIdStrategy(): EntityIdStrategy { return this.activeConfig.entityIdStrategy; } @@ -112,14 +98,6 @@ export class ConfigService implements VendureConfig { return this.activeConfig.customFields; } - get middleware(): Array<{ handler: RequestHandler; route: string }> { - return this.activeConfig.middleware; - } - - get apolloServerPlugins(): PluginDefinition[] { - return this.activeConfig.apolloServerPlugins; - } - get plugins(): Array> { return this.activeConfig.plugins; } diff --git a/packages/core/src/config/default-config.ts b/packages/core/src/config/default-config.ts index 5c9ca59438..eb33eef5bc 100644 --- a/packages/core/src/config/default-config.ts +++ b/packages/core/src/config/default-config.ts @@ -29,16 +29,22 @@ import { RuntimeVendureConfig } from './vendure-config'; * @docsCategory configuration */ export const defaultConfig: RuntimeVendureConfig = { - channelTokenKey: 'vendure-token', defaultChannelToken: null, defaultLanguageCode: LanguageCode.en, - hostname: '', - port: 3000, - cors: { - origin: true, - credentials: true, - }, logger: new DefaultLogger(), + apiOptions: { + hostname: '', + port: 3000, + adminApiPath: 'admin-api', + shopApiPath: 'shop-api', + channelTokenKey: 'vendure-token', + cors: { + origin: true, + credentials: true, + }, + middleware: [], + apolloServerPlugins: [], + }, authOptions: { disableAuth: false, tokenMethod: 'cookie', @@ -51,8 +57,7 @@ export const defaultConfig: RuntimeVendureConfig = { catalogOptions: { collectionFilters: defaultCollectionFilters, }, - adminApiPath: 'admin-api', - shopApiPath: 'shop-api', + entityIdStrategy: new AutoIncrementIdStrategy(), assetOptions: { assetNamingStrategy: new DefaultAssetNamingStrategy(), @@ -116,7 +121,5 @@ export const defaultConfig: RuntimeVendureConfig = { ProductVariant: [], User: [], }, - middleware: [], - apolloServerPlugins: [], plugins: [], }; diff --git a/packages/core/src/config/vendure-config.ts b/packages/core/src/config/vendure-config.ts index 92816b2aeb..e6e4aaf0af 100644 --- a/packages/core/src/config/vendure-config.ts +++ b/packages/core/src/config/vendure-config.ts @@ -29,6 +29,78 @@ import { ShippingEligibilityChecker } from './shipping-method/shipping-eligibili import { TaxCalculationStrategy } from './tax/tax-calculation-strategy'; import { TaxZoneStrategy } from './tax/tax-zone-strategy'; +/** + * @description + * The ApiOptions define how the Vendure GraphQL APIs are exposed, as well as allowing the API layer + * to be extended with middleware. + * + * @docsCategory configuration + */ +export interface ApiOptions { + /** + * @description + * Set the hostname of the server. If not set, the server will be available on localhost. + * + * @default '' + */ + hostname?: string; + /** + * @description + * Which port the Vendure server should listen on. + * + * @default 3000 + */ + port: number; + /** + * @description + * The path to the admin GraphQL API. + * + * @default 'admin-api' + */ + adminApiPath?: string; + /** + * @description + * The path to the admin GraphQL API. + * + * @default 'shop-api' + */ + shopApiPath?: string; + /** + * @description + * The name of the property which contains the token of the + * active channel. This property can be included either in + * the request header or as a query string. + * + * @default 'vendure-token' + */ + channelTokenKey?: string; + /** + * @description + * Set the CORS handling for the server. See the [express CORS docs](https://github.com/expressjs/cors#configuration-options). + * + * @default { origin: true, credentials: true } + */ + cors?: boolean | CorsOptions; + /** + * @description + * Custom Express middleware for the server. + * + * @default [] + */ + middleware?: Array<{ handler: RequestHandler; route: string }>; + /** + * @description + * Custom [ApolloServerPlugins](https://www.apollographql.com/docs/apollo-server/integrations/plugins/) which + * allow the extension of the Apollo Server, which is the underlying GraphQL server used by Vendure. + * + * Apollo plugins can be used e.g. to perform custom data transformations on incoming operations or outgoing + * data. + * + * @default [] + */ + apolloServerPlugins?: PluginDefinition[]; +} + /** * @description * The AuthOptions define how authentication is managed. @@ -431,18 +503,9 @@ export interface JobQueueOptions { export interface VendureConfig { /** * @description - * The path to the admin GraphQL API. - * - * @default 'admin-api' - */ - adminApiPath?: string; - /** - * @description - * The path to the admin GraphQL API. * - * @default 'shop-api' */ - shopApiPath?: string; + apiOptions: ApiOptions; /** * @description * Configuration for the handling of Assets. @@ -458,22 +521,6 @@ export interface VendureConfig { * Configuration for Products and Collections. */ catalogOptions?: CatalogOptions; - /** - * @description - * The name of the property which contains the token of the - * active channel. This property can be included either in - * the request header or as a query string. - * - * @default 'vendure-token' - */ - channelTokenKey?: string; - /** - * @description - * Set the CORS handling for the server. See the [express CORS docs](https://github.com/expressjs/cors#configuration-options). - * - * @default { origin: true, credentials: true } - */ - cors?: boolean | CorsOptions; /** * @description * Defines custom fields which can be used to extend the built-in entities. @@ -511,13 +558,6 @@ export interface VendureConfig { * @default new AutoIncrementIdStrategy() */ entityIdStrategy?: EntityIdStrategy; - /** - * @description - * Set the hostname of the server. If not set, the server will be available on localhost. - * - * @default '' - */ - hostname?: string; /** * @description * Configuration settings for data import and export. @@ -528,24 +568,6 @@ export interface VendureConfig { * Configuration settings governing how orders are handled. */ orderOptions?: OrderOptions; - /** - * @description - * Custom Express middleware for the server. - * - * @default [] - */ - middleware?: Array<{ handler: RequestHandler; route: string }>; - /** - * @description - * Custom [ApolloServerPlugins](https://www.apollographql.com/docs/apollo-server/integrations/plugins/) which - * allow the extension of the Apollo Server, which is the underlying GraphQL server used by Vendure. - * - * Apollo plugins can be used e.g. to perform custom data transformations on incoming operations or outgoing - * data. - * - * @default [] - */ - apolloServerPlugins?: PluginDefinition[]; /** * @description * Configures available payment processing methods. @@ -558,13 +580,6 @@ export interface VendureConfig { * @default [] */ plugins?: Array>; - /** - * @description - * Which port the Vendure server should listen on. - * - * @default 3000 - */ - port: number; /** * @description * Configures the Conditions and Actions available when creating Promotions. @@ -607,6 +622,7 @@ export interface VendureConfig { * @docsCategory configuration */ export interface RuntimeVendureConfig extends Required { + apiOptions: Required; assetOptions: Required; authOptions: Required; customFields: Required; diff --git a/packages/core/src/plugin/plugin-utils.ts b/packages/core/src/plugin/plugin-utils.ts index 6100b61584..d66c5b0d64 100644 --- a/packages/core/src/plugin/plugin-utils.ts +++ b/packages/core/src/plugin/plugin-utils.ts @@ -1,7 +1,7 @@ import { RequestHandler } from 'express'; import { createProxyMiddleware } from 'http-proxy-middleware'; -import { Logger, VendureConfig } from '../config'; +import { Logger, RuntimeVendureConfig, VendureConfig } from '../config'; /** * @description @@ -17,7 +17,7 @@ import { Logger, VendureConfig } from '../config'; * // route of the main Vendure server. * \@VendurePlugin({ * configure: (config: Required) => { - * config.middleware.push({ + * config.apiOptions.middleware.push({ * handler: createProxyHandler({ * label: 'Admin UI', * route: 'my-plugin', @@ -110,15 +110,16 @@ export interface ProxyOptions { /** * If any proxy middleware has been set up using the createProxyHandler function, log this information. */ -export function logProxyMiddlewares(config: VendureConfig) { - for (const middleware of config.middleware || []) { +export function logProxyMiddlewares(config: RuntimeVendureConfig) { + const {} = config.apiOptions; + for (const middleware of config.apiOptions.middleware || []) { if ((middleware.handler as any).proxyMiddleware) { const { port, hostname, label, route, basePath } = (middleware.handler as any) .proxyMiddleware as ProxyOptions; Logger.info( - `${label}: http://${config.hostname || 'localhost'}:${config.port}/${route}/ -> http://${ - hostname || 'localhost' - }:${port}${basePath ? `/${basePath}` : ''}`, + `${label}: http://${config.apiOptions.hostname || 'localhost'}:${ + config.apiOptions.port + }/${route}/ -> http://${hostname || 'localhost'}:${port}${basePath ? `/${basePath}` : ''}`, ); } } diff --git a/packages/create/templates/vendure-config.hbs b/packages/create/templates/vendure-config.hbs index c5c37922ff..c2b74d7faa 100644 --- a/packages/create/templates/vendure-config.hbs +++ b/packages/create/templates/vendure-config.hbs @@ -26,12 +26,14 @@ const path = require('path'); {{/if}} {{#if isTs}}export {{/if}}const config{{#if isTs}}: VendureConfig{{/if}} = { + apiOptions: { + port: 3000, + adminApiPath: 'admin-api', + shopApiPath: 'shop-api', + }, authOptions: { sessionSecret: '{{ sessionSecret }}', }, - port: 3000, - adminApiPath: 'admin-api', - shopApiPath: 'shop-api', dbConnectionOptions: { type: '{{ dbType }}', {{#if requiresConnection}} diff --git a/packages/dev-server/dev-config.ts b/packages/dev-server/dev-config.ts index 66e164e7d1..70e15054bc 100644 --- a/packages/dev-server/dev-config.ts +++ b/packages/dev-server/dev-config.ts @@ -19,14 +19,15 @@ import { ConnectionOptions } from 'typeorm'; * Config settings used during development */ export const devConfig: VendureConfig = { + apiOptions: { + port: API_PORT, + adminApiPath: ADMIN_API_PATH, + }, authOptions: { disableAuth: false, sessionSecret: 'some-secret', requireVerification: true, }, - port: API_PORT, - adminApiPath: ADMIN_API_PATH, - shopApiPath: SHOP_API_PATH, dbConnectionOptions: { synchronize: false, logging: false, diff --git a/packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts b/packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts index 5d95177c11..01e7e4fb75 100644 --- a/packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts +++ b/packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts @@ -77,7 +77,9 @@ if (process.env.CI) { describe('Elasticsearch plugin', () => { const { server, adminClient, shopClient } = createTestEnvironment( mergeConfig(testConfig, { - port: 4050, + apiOptions: { + port: 4050, + }, workerOptions: { options: { port: 4055, diff --git a/packages/email-plugin/src/plugin.ts b/packages/email-plugin/src/plugin.ts index 5466156c87..806f4e999a 100644 --- a/packages/email-plugin/src/plugin.ts +++ b/packages/email-plugin/src/plugin.ts @@ -143,7 +143,7 @@ import { imports: [PluginCommonModule], providers: [{ provide: EMAIL_PLUGIN_OPTIONS, useFactory: () => EmailPlugin.options }], workers: [EmailProcessorController], - configuration: (config) => EmailPlugin.configure(config), + configuration: config => EmailPlugin.configure(config), }) export class EmailPlugin implements OnVendureBootstrap, OnVendureClose { private static options: EmailPluginOptions | EmailPluginDevModeOptions; @@ -172,7 +172,7 @@ export class EmailPlugin implements OnVendureBootstrap, OnVendureClose { static configure(config: RuntimeVendureConfig): RuntimeVendureConfig { if (isDevModeOptions(this.options) && this.options.mailboxPort !== undefined) { const route = 'mailbox'; - config.middleware.push({ + config.apiOptions.middleware.push({ handler: createProxyHandler({ port: this.options.mailboxPort, route, label: 'Dev Mailbox' }), route, }); @@ -201,10 +201,10 @@ export class EmailPlugin implements OnVendureBootstrap, OnVendureClose { this.jobQueue = this.jobQueueService.createQueue({ name: 'send-email', concurrency: 5, - process: (job) => { + process: job => { this.workerService.send(new EmailWorkerMessage(job.data)).subscribe({ complete: () => job.complete(), - error: (err) => job.fail(err), + error: err => job.fail(err), }); }, }); @@ -220,7 +220,7 @@ export class EmailPlugin implements OnVendureBootstrap, OnVendureClose { private async setupEventSubscribers() { for (const handler of EmailPlugin.options.handlers) { - this.eventBus.ofType(handler.event).subscribe((event) => { + this.eventBus.ofType(handler.event).subscribe(event => { return this.handleEvent(handler, event); }); } @@ -237,7 +237,7 @@ export class EmailPlugin implements OnVendureBootstrap, OnVendureClose { (event as EventWithAsyncData).data = await handler._loadDataFn({ event, connection: this.connection, - inject: (t) => this.moduleRef.get(t, { strict: false }), + inject: t => this.moduleRef.get(t, { strict: false }), }); } const result = await handler.handle(event as any, EmailPlugin.options.globalTemplateVars); diff --git a/packages/testing/src/config/test-config.ts b/packages/testing/src/config/test-config.ts index 57245746f6..d2061983cb 100644 --- a/packages/testing/src/config/test-config.ts +++ b/packages/testing/src/config/test-config.ts @@ -28,10 +28,12 @@ export const E2E_DEFAULT_CHANNEL_TOKEN = 'e2e-default-channel'; * @docsCategory testing */ export const testConfig: Required = mergeConfig(defaultConfig, { - port: 3050, - adminApiPath: ADMIN_API_PATH, - shopApiPath: SHOP_API_PATH, - cors: true, + apiOptions: { + port: 3050, + adminApiPath: ADMIN_API_PATH, + shopApiPath: SHOP_API_PATH, + cors: true, + }, defaultChannelToken: E2E_DEFAULT_CHANNEL_TOKEN, authOptions: { sessionSecret: 'some-secret', diff --git a/packages/testing/src/create-test-environment.ts b/packages/testing/src/create-test-environment.ts index e7713256a7..a26303cbba 100644 --- a/packages/testing/src/create-test-environment.ts +++ b/packages/testing/src/create-test-environment.ts @@ -59,14 +59,9 @@ export interface TestEnvironment { */ export function createTestEnvironment(config: Required): TestEnvironment { const server = new TestServer(config); - const adminClient = new SimpleGraphQLClient( - config, - `http://localhost:${config.port}/${config.adminApiPath}`, - ); - const shopClient = new SimpleGraphQLClient( - config, - `http://localhost:${config.port}/${config.shopApiPath}`, - ); + const { port, adminApiPath, shopApiPath } = config.apiOptions; + const adminClient = new SimpleGraphQLClient(config, `http://localhost:${port}/${adminApiPath}`); + const shopClient = new SimpleGraphQLClient(config, `http://localhost:${port}/${shopApiPath}`); return { server, adminClient, diff --git a/packages/testing/src/data-population/populate-customers.ts b/packages/testing/src/data-population/populate-customers.ts index c46ca21aeb..6954247e1d 100644 --- a/packages/testing/src/data-population/populate-customers.ts +++ b/packages/testing/src/data-population/populate-customers.ts @@ -13,7 +13,7 @@ export async function populateCustomers( logging: boolean = false, simpleGraphQLClient = new SimpleGraphQLClient( config, - `http://localhost:${config.port}/${config.adminApiPath}`, + `http://localhost:${config.apiOptions.port}/${config.apiOptions.adminApiPath}`, ), ) { const client = simpleGraphQLClient; diff --git a/packages/testing/src/simple-graphql-client.ts b/packages/testing/src/simple-graphql-client.ts index 73c3c86ee6..2ce30482fc 100644 --- a/packages/testing/src/simple-graphql-client.ts +++ b/packages/testing/src/simple-graphql-client.ts @@ -53,7 +53,9 @@ export class SimpleGraphQLClient { */ setChannelToken(token: string | null) { this.channelToken = token; - this.headers[this.vendureConfig.channelTokenKey] = this.channelToken; + if (this.vendureConfig.apiOptions.channelTokenKey) { + this.headers[this.vendureConfig.apiOptions.channelTokenKey] = this.channelToken; + } } /** @@ -228,7 +230,7 @@ export class SimpleGraphQLClient { curl.setOpt(Curl.option.HTTPPOST, processedPostData); curl.setOpt(Curl.option.HTTPHEADER, [ `Authorization: Bearer ${this.authToken}`, - `${this.vendureConfig.channelTokenKey}: ${this.channelToken}`, + `${this.vendureConfig.apiOptions.channelTokenKey}: ${this.channelToken}`, ]); curl.perform(); curl.on('end', (statusCode: any, body: any) => { diff --git a/packages/testing/src/test-server.ts b/packages/testing/src/test-server.ts index dc88a31a12..f40a1c4bf9 100644 --- a/packages/testing/src/test-server.ts +++ b/packages/testing/src/test-server.ts @@ -71,7 +71,7 @@ export class TestServer { */ async destroy() { // allow a grace period of any outstanding async tasks to complete - await new Promise((resolve) => global.setTimeout(resolve, 500)); + await new Promise(resolve => global.setTimeout(resolve, 500)); await this.app.close(); if (this.worker) { await this.worker.close(); @@ -130,11 +130,11 @@ export class TestServer { try { DefaultLogger.hideNestBoostrapLogs(); const app = await NestFactory.create(appModule.AppModule, { - cors: config.cors, + cors: config.apiOptions.cors, logger: new Logger(), }); let worker: INestMicroservice | undefined; - await app.listen(config.port); + await app.listen(config.apiOptions.port); if (config.workerOptions.runInMainProcess) { const workerModule = await import('@vendure/core/dist/worker/worker.module'); worker = await NestFactory.createMicroservice(workerModule.WorkerModule, {