From c9cc1e2fb71fb906ef70498ff49a6657987f5123 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sun, 19 Jun 2022 21:23:56 +0200 Subject: [PATCH] refactor: extract common app setup code This allows the E2E tests and the real app to share the same setup. Fixes #2083 Signed-off-by: David Mehren --- src/app-init.ts | 80 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.ts | 66 ++++++++++---------------------------- test/test-setup.ts | 15 ++++----- 3 files changed, 104 insertions(+), 57 deletions(-) create mode 100644 src/app-init.ts diff --git a/src/app-init.ts b/src/app-init.ts new file mode 100644 index 000000000..742bf99f8 --- /dev/null +++ b/src/app-init.ts @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { HttpAdapterHost } from '@nestjs/core'; +import { NestExpressApplication } from '@nestjs/platform-express'; + +import { AppConfig } from './config/app.config'; +import { AuthConfig } from './config/auth.config'; +import { DatabaseConfig } from './config/database.config'; +import { MediaConfig } from './config/media.config'; +import { ErrorExceptionMapping } from './errors/error-mapping'; +import { ConsoleLoggerService } from './logger/console-logger.service'; +import { BackendType } from './media/backends/backend-type.enum'; +import { setupSpecialGroups } from './utils/createSpecialGroups'; +import { setupFrontendProxy } from './utils/frontend-integration'; +import { setupSessionMiddleware } from './utils/session'; +import { setupValidationPipe } from './utils/setup-pipes'; +import { setupPrivateApiDocs, setupPublicApiDocs } from './utils/swagger'; + +/** + * Common setup function which is called by main.ts and the E2E tests. + */ +export async function setupApp( + app: NestExpressApplication, + appConfig: AppConfig, + authConfig: AuthConfig, + databaseConfig: DatabaseConfig, + mediaConfig: MediaConfig, + logger: ConsoleLoggerService, +): Promise { + setupPublicApiDocs(app); + logger.log( + `Serving OpenAPI docs for public api under '/apidoc'`, + 'AppBootstrap', + ); + + if (process.env.NODE_ENV === 'development') { + setupPrivateApiDocs(app); + logger.log( + `Serving OpenAPI docs for private api under '/private/apidoc'`, + 'AppBootstrap', + ); + + await setupFrontendProxy(app, logger); + } + + await setupSpecialGroups(app); + + setupSessionMiddleware(app, authConfig, databaseConfig); + + app.enableCors({ + origin: appConfig.rendererOrigin, + }); + logger.log(`Enabling CORS for '${appConfig.rendererOrigin}'`, 'AppBootstrap'); + + app.useGlobalPipes(setupValidationPipe(logger)); + + if (mediaConfig.backend.use === BackendType.FILESYSTEM) { + logger.log( + `Serving the local folder '${mediaConfig.backend.filesystem.uploadPath}' under '/uploads'`, + 'AppBootstrap', + ); + app.useStaticAssets(mediaConfig.backend.filesystem.uploadPath, { + prefix: '/uploads/', + }); + } + + logger.log( + `Serving the local folder 'public' under '/public'`, + 'AppBootstrap', + ); + app.useStaticAssets('public', { + prefix: '/public/', + }); + + const { httpAdapter } = app.get(HttpAdapterHost); + app.useGlobalFilters(new ErrorExceptionMapping(httpAdapter)); +} diff --git a/src/main.ts b/src/main.ts index 291ebad82..d5fb234a1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,84 +5,52 @@ */ import { LogLevel } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { HttpAdapterHost, NestFactory } from '@nestjs/core'; +import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; +import { setupApp } from './app-init'; import { AppModule } from './app.module'; import { AppConfig } from './config/app.config'; import { AuthConfig } from './config/auth.config'; import { DatabaseConfig } from './config/database.config'; import { MediaConfig } from './config/media.config'; -import { ErrorExceptionMapping } from './errors/error-mapping'; import { ConsoleLoggerService } from './logger/console-logger.service'; -import { BackendType } from './media/backends/backend-type.enum'; -import { setupSpecialGroups } from './utils/createSpecialGroups'; -import { setupFrontendProxy } from './utils/frontend-integration'; -import { setupSessionMiddleware } from './utils/session'; -import { setupValidationPipe } from './utils/setup-pipes'; -import { setupPrivateApiDocs, setupPublicApiDocs } from './utils/swagger'; async function bootstrap(): Promise { + // Initialize AppModule const app = await NestFactory.create(AppModule, { logger: ['error', 'warn', 'log'] as LogLevel[], bufferLogs: true, }); + + // Set up our custom logger const logger = await app.resolve(ConsoleLoggerService); logger.log('Switching logger', 'AppBootstrap'); app.useLogger(logger); + + // Initialize config and abort if we don't have a valid config const configService = app.get(ConfigService); const appConfig = configService.get('appConfig'); const databaseConfig = configService.get('databaseConfig'); const authConfig = configService.get('authConfig'); const mediaConfig = configService.get('mediaConfig'); - if (!appConfig || !databaseConfig || !authConfig || !mediaConfig) { logger.error('Could not initialize config, aborting.', 'AppBootstrap'); process.exit(1); } - setupPublicApiDocs(app); - logger.log( - `Serving OpenAPI docs for public api under '/apidoc'`, - 'AppBootstrap', + // Call common setup function which handles the rest + // Setup code must be added there! + await setupApp( + app, + appConfig, + authConfig, + databaseConfig, + mediaConfig, + logger, ); - if (process.env.NODE_ENV === 'development') { - setupPrivateApiDocs(app); - logger.log( - `Serving OpenAPI docs for private api under '/private/apidoc'`, - 'AppBootstrap', - ); - await setupFrontendProxy(app, logger); - } - await setupSpecialGroups(app); - - setupSessionMiddleware(app, authConfig, databaseConfig); - - app.enableCors({ - origin: appConfig.rendererOrigin, - }); - logger.log(`Enabling CORS for '${appConfig.rendererOrigin}'`, 'AppBootstrap'); - - app.useGlobalPipes(setupValidationPipe(logger)); - if (mediaConfig.backend.use === BackendType.FILESYSTEM) { - logger.log( - `Serving the local folder '${mediaConfig.backend.filesystem.uploadPath}' under '/uploads'`, - 'AppBootstrap', - ); - app.useStaticAssets(mediaConfig.backend.filesystem.uploadPath, { - prefix: '/uploads/', - }); - } - logger.log( - `Serving the local folder 'public' under '/public'`, - 'AppBootstrap', - ); - app.useStaticAssets('public', { - prefix: '/public/', - }); - const { httpAdapter } = app.get(HttpAdapterHost); - app.useGlobalFilters(new ErrorExceptionMapping(httpAdapter)); + // Start the server await app.listen(appConfig.port); logger.log(`Listening on port ${appConfig.port}`, 'AppBootstrap'); } diff --git a/test/test-setup.ts b/test/test-setup.ts index 8725188f2..b28a1f5ab 100644 --- a/test/test-setup.ts +++ b/test/test-setup.ts @@ -12,14 +12,17 @@ import { Connection, createConnection } from 'typeorm'; import { PrivateApiModule } from '../src/api/private/private-api.module'; import { PublicApiModule } from '../src/api/public/public-api.module'; +import { setupApp } from '../src/app-init'; import { AuthTokenWithSecretDto } from '../src/auth/auth-token.dto'; import { AuthModule } from '../src/auth/auth.module'; import { AuthService } from '../src/auth/auth.service'; import { MockAuthGuard } from '../src/auth/mock-auth.guard'; import { TokenAuthGuard } from '../src/auth/token.strategy'; import { AuthorsModule } from '../src/authors/authors.module'; +import { AppConfig } from '../src/config/app.config'; import { AuthConfig } from '../src/config/auth.config'; import { DatabaseConfig } from '../src/config/database.config'; +import { MediaConfig } from '../src/config/media.config'; import appConfigMock from '../src/config/mock/app.config.mock'; import authConfigMock from '../src/config/mock/auth.config.mock'; import customizationConfigMock from '../src/config/mock/customization.config.mock'; @@ -50,8 +53,6 @@ import { RevisionsModule } from '../src/revisions/revisions.module'; import { User } from '../src/users/user.entity'; import { UsersModule } from '../src/users/users.module'; import { UsersService } from '../src/users/users.service'; -import { setupSessionMiddleware } from '../src/utils/session'; -import { setupValidationPipe } from '../src/utils/setup-pipes'; export class TestSetup { moduleRef: TestingModule; @@ -271,15 +272,13 @@ export class TestSetupBuilder { this.testSetup.app = this.testSetup.moduleRef.createNestApplication(); - setupSessionMiddleware( + await setupApp( this.testSetup.app, + this.testSetup.configService.get('appConfig'), this.testSetup.configService.get('authConfig'), this.testSetup.configService.get('databaseConfig'), - ); - this.testSetup.app.useGlobalPipes( - setupValidationPipe( - await this.testSetup.app.resolve(ConsoleLoggerService), - ), + this.testSetup.configService.get('mediaConfig'), + await this.testSetup.app.resolve(ConsoleLoggerService), ); for (const setupFunction of this.setupPostCompile) {