'use strict' import { getLogger } from 'frontblock-generic/Types'; 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 { 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 { Shoutbox } from '../Components/Shoutbox/Shoutbox'; const logger = getLogger("admin", 'debug') @RootComponent({ injectable: IAdmin, injects: [ GuildManager, ItemManager, RaidManager, CharacterManager, UserManager, Shoutbox ] }) export class FrontworkAdmin implements TableDefinitionExporter, IAdmin { knex:Knex config: RPCConfigLoader rpcServer: RPCServer private express private httpServer constructor(private frontworkComponents: FrontworkComponent[] = []){ this.config = new RPCConfigLoader({ name: "FrontworkAdminConf", getDefaultConfig: () => { return { httpPort: 8080, eventBusConf: {}, dbConf: { client: 'sqlite3', connection: { filename: Path.join(__dirname, "data/frontworkAdmin.sqlite") }, useNullAsDefault: true, } } } }, './config', this.configChangeHandler) } async start(){ await this.makeKnex() this.startWebsocket() await Promise.all( this.frontworkComponents.map(c => c.initialize?c.initialize():undefined )) logger.debug(this.frontworkComponents.length+" components initialized") this.startWebserver() } stop(){ Promise.race([ Promise.all( this.frontworkComponents.map(c => c.stop?c.stop():undefined )), new Promise((res, rej) => { setTimeout(res, 250);}) ]).catch(e => logger.warn(e)) .finally(() => { 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 ].flatMap(exporter => exporter.getTableDefinitions()) } private startWebsocket(){ this.rpcServer = new RPCServer(20000, [ ...this.frontworkComponents, { name: "debug", exportRPCs: () => [{ name: 'dumpDb', call: async (table) => await this.knex(table).select('*') }] } ], { visibility: '0.0.0.0' }) logger.debug("Websocket up on", 20000) } private startWebserver(){ if(this.httpServer != null || this.express != null){ logger.warn("Webserver is already running") return } let port:number = this.config.getConfig().httpPort this.express = express() this.express.use('/', express.static('dist/static')) /** * get the compiled FrontendPlugins.js */ this.express.get('/plugins/:id'+".js", async (request, response) => { const pth = Path.resolve("plugins/"+request.params.id, "FrontendPlugin.js"); const file = await fs.readFile(pth) const frontend = file.toString() response.status(200) response.set('Content-Type', 'application/javascript') response.send(frontend) }) /** * serve the index.html from the static folder */ this.express.get("/", (request, response) => { response.status(200) response.sendFile('index.html'); }) /** * redirect all the other traffic to the single page app */ this.express.get("*", (request, response) => { response.status(301) response.redirect('/') }) this.httpServer = new http.Server(this.express) this.httpServer.listen(port, () => { logger.info('Admin panel listening for HTTP on *'+port) }) } private stopWebserver(){ if(this.httpServer == null || this.express == null){ logger.warn("Webserver is not running") return } this.httpServer.close() this.httpServer = null this.express = null logger.info("Webserver stopped") } async makeKnex():Promise{ const conf:Knex.Config = this.config.getConfigKey("dbConf") logger.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) }) ) return this.knex } } process.on( 'SIGINT', function() { logger.info("Shutting down from SIGINT (Ctrl-C)" ); Injector.resolve(FrontworkAdmin).stop() })