Skip to content

Commit

Permalink
feat(core): Group api options in VendureConfig
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
michaelbromley committed May 1, 2020
1 parent ed6f68f commit 6904743
Show file tree
Hide file tree
Showing 24 changed files with 215 additions and 177 deletions.
13 changes: 8 additions & 5 deletions packages/admin-ui-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ describe('AssetServerPlugin', () => {

const { server, adminClient, shopClient } = createTestEnvironment(
mergeConfig(testConfig, {
port: 5050,
apiOptions: {
port: 5050,
},
workerOptions: {
options: {
port: 5055,
Expand Down
2 changes: 1 addition & 1 deletion packages/asset-server-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down
14 changes: 9 additions & 5 deletions packages/core/e2e/apollo-server-plugin.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { mergeConfig } from '@vendure/core';
import {
ApolloServerPlugin,
GraphQLRequestContext,
Expand All @@ -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 {
Expand Down Expand Up @@ -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({
Expand Down
8 changes: 4 additions & 4 deletions packages/core/e2e/plugin.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -164,15 +164,15 @@ 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();

expect(body).toBe('true');
});
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();

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/api/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ 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'),
),
resolverModule: ShopApiModule,
})),
configureGraphQLModule(configService => ({
apiType: 'admin',
apiPath: configService.adminApiPath,
apiPath: configService.apiOptions.adminApiPath,
typePaths: ['type', 'admin-api', 'common'].map(p =>
path.join(__dirname, 'schema', p, '*.graphql'),
),
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/api/common/request-context.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]) {
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/api/config/configure-graphql-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ async function createGraphQLOptions(
new IdCodecPlugin(idCodecService),
new TranslateErrorsPlugin(i18nService),
new AssetInterceptorPlugin(configService),
...configService.apolloServerPlugins,
...configService.apiOptions.apolloServerPlugins,
],
} as GqlModuleOptions;

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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);
Expand All @@ -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] = [];
}
Expand Down
41 changes: 32 additions & 9 deletions packages/core/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ export async function bootstrap(userConfig: Partial<VendureConfig>): Promise<INe
// config, so that they are available when the AppModule decorator is evaluated.
// tslint:disable-next-line:whitespace
const appModule = await import('./app.module');
const { hostname, port, cors } = config.apiOptions;
DefaultLogger.hideNestBoostrapLogs();
const app = await NestFactory.create(appModule.AppModule, {
cors: config.cors,
cors,
logger: new Logger(),
});
DefaultLogger.restoreOriginalLogLevel();
app.useLogger(new Logger());
await app.listen(config.port, config.hostname);
await app.listen(port, hostname || '');
app.enableShutdownHooks();
if (config.workerOptions.runInMainProcess) {
try {
Expand Down Expand Up @@ -142,8 +143,9 @@ async function bootstrapWorkerInternal(
*/
export async function preBootstrapConfig(
userConfig: Partial<VendureConfig>,
): Promise<ReadOnlyRequired<VendureConfig>> {
): Promise<Readonly<RuntimeVendureConfig>> {
if (userConfig) {
checkForDeprecatedOptions(userConfig);
setConfig(userConfig);
}

Expand Down Expand Up @@ -207,10 +209,10 @@ export async function getAllEntities(userConfig: Partial<VendureConfig>): 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<VendureConfig>) {
function setExposedHeaders(config: Readonly<RuntimeVendureConfig>) {
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[];
Expand Down Expand Up @@ -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(`=================================================`);
}
Expand All @@ -284,3 +287,23 @@ function disableSynchronize(userConfig: ReadOnlyRequired<VendureConfig>): ReadOn
} as ConnectionOptions;
return config;
}

function checkForDeprecatedOptions(config: Partial<VendureConfig>) {
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.`,
);
}
}
20 changes: 12 additions & 8 deletions packages/core/src/config/config.service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import { ConfigService } from './config.service';
import { EntityIdStrategy, PrimaryKeyType } from './entity-id-strategy/entity-id-strategy';

export class MockConfigService implements MockClass<ConfigService> {
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<any>;
roundingStrategy: {};
entityIdStrategy = new MockIdStrategy();
Expand All @@ -35,10 +40,9 @@ export class MockConfigService implements MockClass<ConfigService> {
orderOptions = {};
workerOptions = {};
customFields = {};
middleware = [];
logger = {} as any;
apolloServerPlugins = [];

plugins = [];
logger = {} as any;
jobQueueOptions = {};
}

Expand Down
36 changes: 7 additions & 29 deletions packages/core/src/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -36,6 +38,10 @@ export class ConfigService implements VendureConfig {
}
}

get apiOptions(): Required<ApiOptions> {
return this.activeConfig.apiOptions;
}

get authOptions(): Required<AuthOptions> {
return this.activeConfig.authOptions;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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<DynamicModule | Type<any>> {
return this.activeConfig.plugins;
}
Expand Down
Loading

0 comments on commit 6904743

Please sign in to comment.