Replace Application class w/ configureApplication

This commit is contained in:
Jerko Steiner 2019-08-31 14:14:11 +07:00
parent 64f5f2f642
commit 8ca6107ce6
6 changed files with 82 additions and 243 deletions

View File

@ -1,155 +0,0 @@
import * as middleware from '../middleware'
import * as routes from '../routes'
import * as rpc from '../rpc'
import * as services from '../services'
import * as team from '../team'
import * as user from '../user'
import cookieParser from 'cookie-parser'
import express from 'express'
import {keys} from 'ts-transformer-keys'
import {AsyncRouter, TransactionalRouter} from '../router'
import {IApplication} from './IApplication'
import {IConfig} from './IConfig'
import {IDatabase} from '../database/IDatabase'
import {ILogger} from '../logger/ILogger'
import {IRoutes, IContext} from '@rondo.dev/common'
import {IServices} from './IServices'
import {ITransactionManager} from '../database/ITransactionManager'
import {loggerFactory} from '../logger'
import {ILoggerFactory} from '@rondo.dev/logger'
import {json} from 'body-parser'
import {jsonrpc} from '@rondo.dev/jsonrpc'
export class Application implements IApplication {
readonly transactionManager: ITransactionManager
readonly server: express.Application
readonly services: IServices
readonly authenticator: middleware.Authenticator
readonly loggerFactory: ILoggerFactory = loggerFactory
constructor(readonly config: IConfig, readonly database: IDatabase) {
this.transactionManager = database.transactionManager
this.services = this.configureServices()
this.authenticator = new middleware.Authenticator(this.services.userService)
this.server = this.createServer()
}
protected configureServices(): IServices {
return {
userService: new services.UserService(this.database),
teamService: new team.TeamService(this.database),
userPermissions: new user.UserPermissions(this.database),
}
}
protected getApiLogger(): ILogger {
return this.loggerFactory.getLogger('api')
}
protected createServer() {
const server = express()
server.set('trust proxy', 1)
server.disable('x-powered-by')
const router = express.Router()
this.configureMiddleware(router)
this.configureRouter(router)
this.configureRPC(router)
this.configureApiErrorHandling(router)
this.configureFrontend(router)
server.use(this.config.app.context, router)
this.configureGlobalErrorHandling(server)
return server
}
protected configureMiddleware(router: express.Router) {
const {transactionManager} = this
const apiLogger = this.getApiLogger()
router.use(new middleware.SessionMiddleware({
transactionManager,
baseUrl: this.config.app.baseUrl,
sessionName: this.config.app.session.name,
sessionSecret: this.config.app.session.secret,
}).handle)
router.use(new middleware.RequestLogger(apiLogger).handle)
router.use(json())
router.use(cookieParser(this.config.app.session.secret))
router.use(new middleware.CSRFMiddleware({
baseUrl: this.config.app.baseUrl,
cookieName: this.config.app.session.name + '_csrf',
}).handle)
router.use(new middleware.Transaction(this.database.namespace).handle)
router.use(this.authenticator.handle)
}
protected configureRouter(router: express.Router) {
// TODO use /api for LoginRoutes
router.use('/app', routes.application)
router.use('/api', new routes.LoginRoutes(
this.services.userService,
this.authenticator,
this.createTransactionalRouter(),
).handle)
router.use('/api', new routes.UserRoutes(
this.services.userService,
this.createTransactionalRouter(),
).handle)
router.use('/api', new team.TeamRoutes(
this.services.teamService,
this.services.userPermissions,
this.createTransactionalRouter(),
).handle)
}
protected getContext(req: express.Request): IContext {
return {user: req.user}
}
protected jsonrpc() {
return jsonrpc(
req => this.getContext(req),
this.getApiLogger(),
(path, service, callback) => this
.database
.transactionManager
.doInNewTransaction(() => callback()),
)
}
protected configureRPC(router: express.Router) {
// Override this method
}
protected configureApiErrorHandling(router: express.Router) {
const apiLogger = this.getApiLogger()
router.use('/api', new middleware.ErrorApiHandler(apiLogger).handle)
}
protected configureFrontend(router: express.Router) {
// Override this method
}
protected configureGlobalErrorHandling(server: express.Application) {
const apiLogger = this.getApiLogger()
server.use(new middleware.ErrorPageHandler(apiLogger).handle)
}
createAsyncRouter<T extends IRoutes>(): AsyncRouter<T> {
return new AsyncRouter<T>()
}
createTransactionalRouter<T extends IRoutes>(): AsyncRouter<T> {
return new TransactionalRouter<T>(this.transactionManager)
}
}

View File

@ -1,16 +1,15 @@
import assert from 'assert' import assert from 'assert'
import {AddressInfo} from 'net' import { createNamespace, Namespace } from 'cls-hooked'
import {Application} from './Application' import { Server } from 'http'
import {Database} from '../database/Database' import { AddressInfo } from 'net'
import {IApplication} from './IApplication' import { Database } from '../database/Database'
import {IBootstrap} from './IBootstrap' import { IDatabase } from '../database/IDatabase'
import {IConfig} from './IConfig' import { loggerFactory, SqlLogger } from '../logger'
import {IDatabase} from '../database/IDatabase'
import {Server} from 'http'
import {SqlLogger, loggerFactory} from '../logger'
import {createNamespace, Namespace} from 'cls-hooked'
import { configureApplication } from './configureApplication' import { configureApplication } from './configureApplication'
import { createApplication } from './createApplication' import { createApplication } from './createApplication'
import { IApplication } from './IApplication'
import { IBootstrap } from './IBootstrap'
import { IConfig } from './IConfig'
export class Bootstrap implements IBootstrap { export class Bootstrap implements IBootstrap {
protected server?: Server protected server?: Server

View File

@ -4,21 +4,10 @@ import { ILogger } from '@rondo.dev/logger'
import { IServices } from './IServices' import { IServices } from './IServices'
import { RequestHandlerParams, ErrorRequestHandler } from 'express-serve-static-core' import { RequestHandlerParams, ErrorRequestHandler } from 'express-serve-static-core'
export interface IFramework { export interface IApplicationMiddleware {
readonly middleware: RequestHandlerParams[] path: string
// TODO remove app, i believe this is used in tests handle: RequestHandlerParams[]
readonly app: RequestHandlerParams[] error?: ErrorRequestHandler
readonly api: RequestHandlerParams[]
readonly apiError: ErrorRequestHandler
readonly frontend: RequestHandlerParams[]
readonly error: ErrorRequestHandler
}
export interface IFrameworkPaths {
readonly middleware: string
readonly app: string
readonly api: string
readonly frontend: string
} }
export interface IApplicationConfig { export interface IApplicationConfig {
@ -26,6 +15,6 @@ export interface IApplicationConfig {
readonly database: IDatabase readonly database: IDatabase
readonly logger: ILogger readonly logger: ILogger
readonly services: IServices readonly services: IServices
readonly paths?: Partial<IFrameworkPaths> readonly globalErrorHandler: ErrorRequestHandler
readonly framework: IFramework readonly framework: Record<string, IApplicationMiddleware>
} }

View File

@ -14,10 +14,14 @@ import { TransactionalRouter } from '../router'
import { IRoutes, IContext } from '@rondo.dev/common' import { IRoutes, IContext } from '@rondo.dev/common'
import { Express } from 'express-serve-static-core' import { Express } from 'express-serve-static-core'
export function configureApplication( export type AppConfigurator<
T extends IApplicationConfig = IApplicationConfig
> = (
config: IConfig, config: IConfig,
database: IDatabase, database: IDatabase,
): IApplicationConfig { ) => T
export const configureApplication: AppConfigurator = (config, database) => {
const logger = loggerFactory.getLogger('api') const logger = loggerFactory.getLogger('api')
@ -33,51 +37,59 @@ export function configureApplication(
const createTransactionalRouter = <T extends IRoutes>() => const createTransactionalRouter = <T extends IRoutes>() =>
new TransactionalRouter<T>(transactionManager) new TransactionalRouter<T>(transactionManager)
const getContext = (req: Express.Request): IContext => ({user: req.user}) const globalErrorHandler = new Middleware.ErrorPageHandler(logger).handle
return { return {
config, config,
database, database,
logger, logger,
services, services,
globalErrorHandler,
framework: { framework: {
middleware: [ middleware: {
new Middleware.SessionMiddleware({ path: '/',
transactionManager, handle: [
baseUrl: config.app.baseUrl, new Middleware.SessionMiddleware({
sessionName: config.app.session.name, transactionManager,
sessionSecret: config.app.session.secret, baseUrl: config.app.baseUrl,
}).handle, sessionName: config.app.session.name,
new Middleware.RequestLogger(logger).handle, sessionSecret: config.app.session.secret,
json(), }).handle,
cookieParser(config.app.session.secret), new Middleware.RequestLogger(logger).handle,
new Middleware.CSRFMiddleware({ json(),
baseUrl: config.app.baseUrl, cookieParser(config.app.session.secret),
cookieName: config.app.session.name + '_csrf', new Middleware.CSRFMiddleware({
}).handle, baseUrl: config.app.baseUrl,
new Middleware.Transaction(database.namespace).handle, cookieName: config.app.session.name + '_csrf',
authenticator.handle, }).handle,
], new Middleware.Transaction(database.namespace).handle,
app: [routes.application], authenticator.handle,
api: [ ],
new routes.LoginRoutes( },
services.userService, app: {
authenticator, path: '/app',
createTransactionalRouter(), handle: [routes.application],
).handle, },
new routes.UserRoutes( api: {
services.userService, path: '/api',
createTransactionalRouter(), handle: [
).handle, new routes.LoginRoutes(
new Team.TeamRoutes( services.userService,
services.teamService, authenticator,
services.userPermissions, createTransactionalRouter(),
createTransactionalRouter(), ).handle,
).handle, new routes.UserRoutes(
], services.userService,
apiError: new Middleware.ErrorApiHandler(logger).handle, createTransactionalRouter(),
frontend: [], ).handle,
error: new Middleware.ErrorPageHandler(logger).handle, new Team.TeamRoutes(
services.teamService,
services.userPermissions,
createTransactionalRouter(),
).handle,
],
error: new Middleware.ErrorApiHandler(logger).handle,
},
}, },
} }
} }

View File

@ -1,37 +1,28 @@
import { IApplicationConfig, IFrameworkPaths } from './IApplicationConfig' import { IApplicationConfig } from './IApplicationConfig'
import { IApplication } from './IApplication' import { IApplication } from './IApplication'
import express from 'express' import express from 'express'
export const defaultPaths: IFrameworkPaths = {
middleware: '/',
app: '/app',
api: '/api',
frontend: '/',
}
export function createApplication(appConfig: IApplicationConfig): IApplication { export function createApplication(appConfig: IApplicationConfig): IApplication {
const {config, database, framework} = appConfig const {config, database, framework} = appConfig
const server = express() const server = express()
const paths = {
...defaultPaths,
...(appConfig.paths || {}),
}
server.set('trust proxy', 1) server.set('trust proxy', 1)
server.disable('x-powered-by') server.disable('x-powered-by')
const router = express.Router() const router = express.Router()
router.use(paths.middleware, ...framework.middleware)
router.use(paths.app, ...framework.app) Object.keys(framework)
router.use(paths.api, ...framework.api) .forEach(name => {
router.use(paths.api, framework.apiError) const {path, handle, error} = framework[name]
if (framework.frontend.length) { router.use(path, ...handle)
router.use(paths.frontend, ...framework.frontend) if (error) {
} router.use(path, error)
}
})
server.use(config.app.context, router) server.use(config.app.context, router)
server.use(framework.error) server.use(appConfig.globalErrorHandler)
return { return {
server, server,
database, database,

View File

@ -1,5 +1,8 @@
export * from './Application' export * from './Application'
export * from './Bootstrap' export * from './Bootstrap'
export * from './IApplication' export * from './IApplication'
export * from './IApplicationConfig'
export * from './IConfig' export * from './IConfig'
export * from './IServices' export * from './IServices'
export * from './configureApplication'
export * from './createApplication'