import express, {Express} from 'express'; import { Middleware, Route, HttpMethod, HttpHandler } from '../base/http'; import { RouteNotSetException } from '../base/exceptions'; import { IncorrectMethodException } from '../base/exceptions'; import { Logger, Message, MessageTypes } from '../base/logger'; import { ServerProperties } from './ServerProperties'; import { i18nLoader, $$ } from '../base/i18n'; import path from 'path'; import fs from 'fs'; import { InvalidMiddlewareException } from '../base/exceptions'; import { InvalidRouteException } from '../base/exceptions'; import { ServerNotInitializedException } from '../base/exceptions'; /** @sealed */ class Server { private instance: Express; private readonly port: number; private readonly logger: Logger; private i18n: i18nLoader; public static readonly i18nDefaultPath = '../resources/i18n.json'; public static readonly defaultMiddlewaresPath = '../middlewares'; public static readonly defaultRoutesPath = '../routes'; private readonly httpHandlers: {[key: string]: HttpHandler}; private initialized: boolean; private readonly i18nPath?: string; private readonly middlewaresPath?: string; private readonly routesPath?: string; private readonly viewEngine?: string; private readonly viewsPath?: string; public constructor(properties: ServerProperties) { this.instance = express(); this.port = properties.port; this.i18n = i18nLoader.getInstance(); if(properties.locale) this.i18n.setLocale(properties.locale); this.logger = new Logger(); this.httpHandlers = {}; this.i18nPath = properties.i18nPath; this.middlewaresPath = properties.middlewaresPath; this.routesPath = properties.routesPath; this.viewEngine = properties.viewEngine; this.viewsPath = properties.viewsPath; this.initialized = false; } public async init(): Promise { if(this.viewEngine) this.instance.set('view engine', this.viewEngine); if(this.viewsPath) this.instance.set('views', this.viewsPath); this.i18n.load(path.resolve(__dirname, Server.i18nDefaultPath)); await this.registerMiddlewares(path.resolve(__dirname, Server.defaultMiddlewaresPath)); await this.registerRoutes(path.resolve(__dirname, Server.defaultRoutesPath)); await this.postInit(); this.initialized = true; return this; } private async postInit(): Promise { if(this.i18nPath) this.i18n.load(this.i18nPath); if(this.middlewaresPath) await this.registerMiddlewares(this.middlewaresPath); if(this.routesPath) await this.registerRoutes(this.routesPath); } private processHttpHandlers(): void { const handlers: HttpHandler[] = []; for(const key in this.httpHandlers) handlers.push(this.httpHandlers[key]); handlers.sort((a, b) => a.getOrder() - b.getOrder()); for(const handler of handlers) { if(handler instanceof Middleware) this.addMiddleware(handler); else if (handler instanceof Route) this.addRoute(handler); } } public addMiddleware(middleware: Middleware): Server { if(middleware.getRoute() != null) this.instance.use(middleware.getRoute(), middleware.getAction()); else this.instance.use(middleware.getAction()); return this; } public addRoute(route: Route): Server { if(route.getRoute() == null) throw new RouteNotSetException(); switch(route.getMethod()) { case HttpMethod.GET: return this.get(route); case HttpMethod.POST: return this.post(route); default: throw new IncorrectMethodException(); } } public logInfo(message: string): void { this.logger.getLogger().info(message); } public logError(message: string): void { this.logger.getLogger().error(message); } public logWarn(message: string): void { this.logger.getLogger().warn(message); } public log(message: Message): void { switch(message.type) { case MessageTypes.WARNING: return this.logWarn(message.text); case MessageTypes.ERROR: return this.logError(message.text); default: return this.logInfo(message.text); } } private get(route: Route): Server { this.instance.get(route.getRoute(), route.getAction()); return this; } private post(route: Route): Server { this.instance.post(route.getRoute(), route.getAction()); return this; } public async registerRoutes(dir: string): Promise { const files = fs.readdirSync(dir); for(const file of files) { if(/\.js$/.test(file)) { const {default: RouteClass} = await import(path.join(dir, file)); if(RouteClass.prototype instanceof Route) { this.httpHandlers[RouteClass.name] = new RouteClass(this); } else throw new InvalidRouteException(file); } } return this; } public async registerMiddlewares(dir: string): Promise { const files = fs.readdirSync(dir); for(const file of files) { if(/\.js$/.test(file)) { const {default: MiddlewareClass} = await import(path.join(dir, file)); if(MiddlewareClass.prototype instanceof Middleware) { this.httpHandlers[MiddlewareClass.name] = new MiddlewareClass(this); } else throw new InvalidMiddlewareException(file); } } return this; } public getLogger(): Logger { return this.logger; } public i18nLoad(path: string): Server { this.i18n.load(path); return this; } public start(callback?: () => any): void { if(!this.initialized) throw new ServerNotInitializedException(); this.processHttpHandlers(); const cb = (): void => { this.logInfo($$('org.crazydoctor.expressts.start', { 'port': this.port })); if(callback) callback(); }; this.instance.listen(this.port, cb); } } export { Server };