'use strict' import { getLogger, Logger, } from "log4js"; import { promises as fs, mkdirSync } from "fs" import { RPCServer } from 'rpclibrary' import * as Path from 'path' import * as Knex from 'knex'; import * as http from 'http'; import * as express from 'express'; import { existsSync } from "fs"; import { GuildManager } from '../Components/Guild/GuildManager'; import { ItemManager } from '../Components/Item/ItemManager'; import { RaidManager } from '../Components/Raid/RaidManager'; import { CharacterManager } from '../Components/Character/CharacterManager'; import { UserManager } from '../Components/User/UserManager'; import { RootComponent } from '../Injector/ServiceDecorator'; import { TableDefinitionExporter } from '../Types/Interfaces'; import { AdminConf, TableDefiniton } from '../Types/Types'; import { RPCConfigLoader } from '../Components/RPCConfigLoader'; import { FrontworkComponent } from '../Types/FrontworkComponent'; import { IAdmin } from './Interface'; import { Injector } from '../Injector/Injector'; import { PubSub } from '../Components/PubSub/PubSub'; @RootComponent({ injectable: IAdmin, injects: [ GuildManager, ItemManager, RaidManager, CharacterManager, UserManager, PubSub ] }) export class FrontworkAdmin implements TableDefinitionExporter, IAdmin { knex: Knex config: RPCConfigLoader rpcServer: RPCServer private express : express.Application private httpServer constructor(private frontworkComponents: FrontworkComponent[] = []) { this.config = new RPCConfigLoader({ name: "FrontworkAdminConf", getDefaultConfig: () => { return { httpPort: 8080, eventBusConf: {}, dbConf: { client: 'sqlite3', connection: { filename: Path.resolve(__dirname, '../../../..', "data/frontworkAdmin.sqlite"), }, migrations: { directory: Path.resolve(__dirname, '../../../..', "migrations"), extension: 'ts' }, useNullAsDefault: true, } } } }, './config', this.configChangeHandler) } async start() { let port: number = this.config.getConfig().httpPort await this.makeKnex() const app = await this.makeExpress() const httpServer = new http.Server(app) await this.startWebsocket(httpServer) httpServer.listen(port) await this.attachAngularSSR(app) getLogger('Admin#startWebsocket').debug("Webserver up on", port) await Promise.all(this.frontworkComponents.map(c => c.initialize ? c.initialize() : undefined)) getLogger('Admin#start').debug(this.frontworkComponents.length + " components initialized") } stop() { Promise.all([ ...this.frontworkComponents.map(c => c.stop ? c.stop() : undefined), ]) .catch(e => getLogger('Admin#stop').warn(e)) .finally(() => { this.rpcServer.close() process.exit(0) }) } protected configChangeHandler = (conf: AdminConf, key?: string) => { if (key === 'dbConf') { this.makeKnex() } } getConfigKey(key: string) { return this.config.getConfigKey(key) } setConfigKey(key: string, value: any) { return this.config.setConfigKey(key, value) } getTableDefinitions(): TableDefiniton[] { return [ ...this.frontworkComponents ] .filter(exp => exp.getTableDefinitions != null) .flatMap(exp => exp.getTableDefinitions()) } private startWebsocket(httpServer: http.Server) { this.rpcServer = new RPCServer([ ...this.frontworkComponents, { name: "debug", RPCs: () => [{ name: 'dumpDb', call: async (table) => await this.knex(table).select('*') }] } ], { errorHandler: (sock, err, rpc, args) => { console.log("RPC", rpc) console.log("ERR", err) console.log("ARGS", args) } }) .attach(httpServer) } private async makeExpress() { if (this.httpServer != null || this.express != null) { getLogger('Admin#startWebserver').warn("Webserver is already running") return } this.express = express() const distFolder = "../../../../dist" this.express.get('*.*', (req, res) => { //console.log('*.*', req.path); try{ const filepath = Path.join(__dirname, distFolder, 'browser', decodeURIComponent(req.path)) if(!existsSync(filepath)){ throw new Error("Bad file access: "+filepath) } res.sendFile(filepath) res.status(200) }catch(e){ getLogger('Admin#startWebserver#serveFile').error(String(e)) res.send("404 NOT FOUND") res.status(404) } }) return this.express } attachAngularSSR = async (express : express.Application) => { const distFolder = "../../../../dist" const ngExpressServer = Path.join(__dirname, distFolder, 'server.js') let port: number = this.config.getConfig().httpPort try { const req = require(distFolder + "/server.js") await req.attachExpress(express, './dist', getLogger('angularSSR#')) getLogger('Admin#startWebserver').debug('Frontend from ' + ngExpressServer + " loaded") } catch (e) { getLogger('Admin#startWebserver').error(e) getLogger('Admin#startWebserver').warn("No angular SSR module was provided in " + ngExpressServer) getLogger('Admin#startWebserver').warn("This is not fatal, but your page will not render on *" + port) } } private stopWebserver() { if (this.httpServer == null || this.express == null) { getLogger('Admin#stopWebserver').warn("Webserver is not running") return } this.httpServer.close() this.httpServer = null this.express = null getLogger('Admin#stopWebserver').info("Webserver stopped") } async makeKnex(): Promise { const conf: Knex.Config = this.config.getConfigKey("dbConf") getLogger('Admin#makeKnex').debug("Making new knex:", conf) if (conf.client === 'sqlite3') { mkdirSync(Path.dirname((conf.connection).filename), { recursive: true }) } this.knex = Knex(conf) if (conf.client === 'sqlite3') { await this.knex.raw('PRAGMA foreign_keys = ON'); } await Promise.all( this.getTableDefinitions() //make unique by name .filter((other, index, self) => index === self.findIndex( (self) => self.name === other.name) ) //create table if not exists .map(async (def) => { const hasTable = await this.knex.schema.hasTable(def.name) if (!hasTable) return await this.knex.schema.createTable(def.name, def.tableBuilder) }) ) await this.knex.migrate.latest().catch(e => { getLogger('Admin#makeKnex').error(e) process.exit(-1) }) return this.knex } } process.on('SIGINT', function () { getLogger('process#SIGINT').info("Shutting down from SIGINT (Ctrl-C)"); Injector.resolve(FrontworkAdmin).stop() })