diff --git a/src/Application.ts b/src/Application.ts index 41a0127..8148afb 100644 --- a/src/Application.ts +++ b/src/Application.ts @@ -214,11 +214,6 @@ export default abstract class Application implements Extendable>(type: Type): C { const module = this.components.find(component => component.constructor === type) || Object.values(this.webSocketListeners).find(listener => listener.constructor === type); diff --git a/src/ApplicationComponent.ts b/src/ApplicationComponent.ts index c8bf416..335f294 100644 --- a/src/ApplicationComponent.ts +++ b/src/ApplicationComponent.ts @@ -15,14 +15,18 @@ export default abstract class ApplicationComponent { public async init?(): Promise; - public async start?(expressApp: Express): Promise; - public async initRoutes?(router: Router): Promise; public async handleRoutes?(router: Router): Promise; + public async start?(expressApp: Express): Promise; + public async stop?(): Promise; + public isReady(): boolean { + return true; + } + protected async prepare(name: string, prepare: () => Promise): Promise { let err; do { diff --git a/src/TestApp.ts b/src/TestApp.ts index c9b5a0b..1832d43 100644 --- a/src/TestApp.ts +++ b/src/TestApp.ts @@ -21,6 +21,7 @@ import FormHelperComponent from "./components/FormHelperComponent.js"; import FrontendToolsComponent from "./components/FrontendToolsComponent.js"; import LogRequestsComponent from "./components/LogRequestsComponent.js"; import MailComponent from "./components/MailComponent.js"; +import MaintenanceComponent from "./components/MaintenanceComponent.js"; import MysqlComponent from "./components/MysqlComponent.js"; import PreviousUrlComponent from "./components/PreviousUrlComponent.js"; import RedisComponent from "./components/RedisComponent.js"; @@ -79,6 +80,9 @@ export default class TestApp extends Application { // Static files this.use(new ServeStaticDirectoryComponent('public')); + // Maintenance + this.use(new MaintenanceComponent()); + // Dynamic views and routes const intermediateDirectory = 'build'; this.use(new FrontendToolsComponent( diff --git a/src/components/MaintenanceComponent.ts b/src/components/MaintenanceComponent.ts index fea7c63..871f228 100644 --- a/src/components/MaintenanceComponent.ts +++ b/src/components/MaintenanceComponent.ts @@ -1,35 +1,28 @@ import config from "config"; import {NextFunction, Request, Response, Router} from "express"; -import Application from "../Application.js"; import ApplicationComponent from "../ApplicationComponent.js"; import {ServiceUnavailableHttpError} from "../HttpError.js"; export default class MaintenanceComponent extends ApplicationComponent { - private readonly application: Application; - private readonly canServe: () => boolean; - - public constructor(application: Application, canServe: () => boolean) { - super(); - this.application = application; - this.canServe = canServe; - } - - public async handleRoutes(router: Router): Promise { + public async initRoutes(router: Router): Promise { router.use((req: Request, res: Response, next: NextFunction) => { if (res.headersSent) { return next(); } - if (!this.application.isReady()) { + if (!this.getApp().isReady()) { res.header({'Retry-After': 60}); res.locals.refresh_after = 5; throw new ServiceUnavailableHttpError(`${config.get('app.name')} is readying up. Please wait a few seconds...`); } - if (!this.canServe()) { - res.locals.refresh_after = 30; - throw new ServiceUnavailableHttpError(`${config.get('app.name')} is unavailable due to failure of dependent services.`); + for (const component of this.getApp().getComponents()) { + if (!component.isReady()) { + res.header({'Retry-After': 60}); + res.locals.refresh_after = 30; + throw new ServiceUnavailableHttpError(`${config.get('app.name')} is unavailable due to failure of dependent services.`); + } } next(); diff --git a/src/components/MysqlComponent.ts b/src/components/MysqlComponent.ts index 393a691..8d8e273 100644 --- a/src/components/MysqlComponent.ts +++ b/src/components/MysqlComponent.ts @@ -12,7 +12,7 @@ export default class MysqlComponent extends ApplicationComponent { await MysqlConnectionManager.endPool(); } - public canServe(): boolean { + public isReady(): boolean { return MysqlConnectionManager.isReady(); } diff --git a/src/components/RedisComponent.ts b/src/components/RedisComponent.ts index ac9f756..9ceb9e9 100644 --- a/src/components/RedisComponent.ts +++ b/src/components/RedisComponent.ts @@ -9,7 +9,7 @@ import {logger} from "../Logger.js"; export default class RedisComponent extends ApplicationComponent implements CacheProvider { private redisClient?: RedisClient; - private store?: Store; + private store: Store = new RedisStore(this); public async start(_app: Express): Promise { this.redisClient = redis.createClient(config.get('redis.port'), config.get('redis.host'), { @@ -18,8 +18,6 @@ export default class RedisComponent extends ApplicationComponent implements Cach this.redisClient.on('error', (err: Error) => { logger.error(err, 'An error occurred with redis.'); }); - - this.store = new RedisStore(this); } public async stop(): Promise { @@ -30,11 +28,10 @@ export default class RedisComponent extends ApplicationComponent implements Cach } public getStore(): Store { - if (!this.store) throw `Redis store was not initialized.`; return this.store; } - public canServe(): boolean { + public isReady(): boolean { return this.redisClient !== undefined && this.redisClient.connected; }