UNPKG

@iredium/butterfly

Version:
576 lines (575 loc) 28.5 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArray = (this && this.__spreadArray) || function (to, from) { for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) to[j] = from[i]; return to; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Butterfly = void 0; var base_error_1 = require("./errors/base_error"); var event_1 = require("./events/event"); var errors_1 = require("./errors"); var route_drawer_1 = require("./routes/route_drawer"); var mongodb_1 = require("./databases/mongodb"); var middlewares_1 = require("./middlewares"); var express = require("express"); var logger = require("morgan"); var bodyParser = require("body-parser"); var dotenv = require("dotenv"); var path = require("path"); var redis_1 = require("./databases/redis"); var cookieParserGenerator = require("cookie-parser"); var DEFAULT_VIEW_ENGINE = 'pug'; var App = /** @class */ (function () { function App(config) { this.booted = false; this.initCompleted = false; this.databases = []; this.hooks = { 'butterfly:setup': [], 'butterfly:ready': [], 'butterfly:registerEventListener': [], 'butterfly:registerViewPaths': [], 'butterfly:registerMiddlewares': [], 'butterfly:drawRoutes': [], 'butterfly:onError': [] }; var routes = config.routes, databases = config.databases, userServiceClass = config.userServiceClass, _a = config.useDefaultLogger, useDefaultLogger = _a === void 0 ? true : _a, _b = config.useViewEngine, useViewEngine = _b === void 0 ? false : _b, _c = config.viewEngine, viewEngine = _c === void 0 ? DEFAULT_VIEW_ENGINE : _c, viewsPaths = config.viewsPaths, errorView = config.errorView, eventListenerMap = config.eventListenerMap; dotenv.config({ path: path.resolve(process.cwd(), config.env['NODE_ENV'] === 'test' ? '.env.test' : '.env') }); this.app = express(); this.routes = routes; this.routeDrawer = route_drawer_1.default; this.userServiceClass = userServiceClass; this.useViewEngine = useViewEngine; this.viewsPaths = viewsPaths || [path.join(process.cwd(), '/views')]; this.errorView = errorView; this.viewEngine = viewEngine; this.modules = config.modules || []; this.useDefaultLogger = useDefaultLogger; this.eventListenerMap = eventListenerMap || []; this.databaseConfigs = databases() || { mongo: { enable: false, host: config.env['MONGO_HOST'], port: config.env['MONGO_PORT'], database: config.env['MONGO_DATABASE'], username: config.env['MONGO_USERNAME'], password: config.env['MONGO_PASSWORD'] } }; this.app.locals.config = config; } App.prototype.init = function () { return __awaiter(this, void 0, void 0, function () { var app; return __generator(this, function (_a) { switch (_a.label) { case 0: app = this.app; if (this.initCompleted) return [2 /*return*/, app]; console.log('booting modules..'); return [4 /*yield*/, this.bootModules()]; case 1: _a.sent(); console.log('registering event listeners..'); return [4 /*yield*/, this.registerEventListener()]; case 2: _a.sent(); console.log('preparing connect instance..'); return [4 /*yield*/, this.setup()]; case 3: _a.sent(); console.log('connecting to databases..'); return [4 /*yield*/, this.connectDatabases()]; case 4: _a.sent(); console.log('registering middlewares..'); return [4 /*yield*/, this.registerMiddlewares()]; case 5: _a.sent(); console.log('drawing routes..'); return [4 /*yield*/, this.drawRoutes()]; case 6: _a.sent(); console.log('registering error middlewares..'); return [4 /*yield*/, this.registerErrorMiddleware()]; case 7: _a.sent(); this.initCompleted = true; console.log('init completed'); return [2 /*return*/, app]; } }); }); }; App.prototype.boot = function () { return __awaiter(this, void 0, void 0, function () { var _a, PORT, app; var _this = this; return __generator(this, function (_b) { switch (_b.label) { case 0: if (this.booted) return [2 /*return*/]; _a = this.app.locals.config.env.PORT, PORT = _a === void 0 ? 8080 : _a; return [4 /*yield*/, this.init()]; case 1: app = _b.sent(); this.server = app.listen(PORT, function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.executeHookHandlers('butterfly:ready', { server: this.server, port: PORT, })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }); this.booted = true; return [2 /*return*/]; } }); }); }; App.prototype.hook = function (name, handler) { if (this.hooks[name]) { this.hooks[name].push(handler); } }; App.prototype.close = function () { var _this = this; return new Promise(function (resolve) { _this.server.close(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.closeDatabases()]; case 1: _a.sent(); resolve(); return [2 /*return*/]; } }); }); }); }); }; App.prototype.closeDatabases = function () { return __awaiter(this, void 0, void 0, function () { var _i, _a, database; return __generator(this, function (_b) { switch (_b.label) { case 0: _i = 0, _a = this.databases; _b.label = 1; case 1: if (!(_i < _a.length)) return [3 /*break*/, 4]; database = _a[_i]; return [4 /*yield*/, database.close()]; case 2: _b.sent(); _b.label = 3; case 3: _i++; return [3 /*break*/, 1]; case 4: return [2 /*return*/]; } }); }); }; App.prototype.buildMiddleware = function (middleware) { return middleware.handleMiddleware({ databases: this.databases }); }; App.prototype.useMiddlewares = function (middlewares) { var app = this.app; for (var _i = 0, middlewares_2 = middlewares; _i < middlewares_2.length; _i++) { var middleware = middlewares_2[_i]; app.use(this.buildMiddleware(middleware)); } }; App.prototype.bootModules = function () { return __awaiter(this, void 0, void 0, function () { var _i, _a, moduleImport, modules, moduleNames, _b, moduleNames_1, moduleName, module_1; var _this = this; return __generator(this, function (_c) { switch (_c.label) { case 0: _i = 0, _a = this.modules; _c.label = 1; case 1: if (!(_i < _a.length)) return [3 /*break*/, 4]; moduleImport = _a[_i]; return [4 /*yield*/, moduleImport()]; case 2: modules = _c.sent(); moduleNames = Object.keys(modules); for (_b = 0, moduleNames_1 = moduleNames; _b < moduleNames_1.length; _b++) { moduleName = moduleNames_1[_b]; module_1 = modules[moduleName]; if (module_1 && typeof module_1 === 'function') { module_1({ hook: function (name, handler) { _this.hook(name, handler); } }); } } _c.label = 3; case 3: _i++; return [3 /*break*/, 1]; case 4: return [2 /*return*/]; } }); }); }; App.prototype.executeHookHandlers = function (name) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return __awaiter(this, void 0, void 0, function () { var handlers, _a, handlers_1, handler; return __generator(this, function (_b) { switch (_b.label) { case 0: handlers = this.hooks[name]; if (!handlers) throw new base_error_1.BaseError('Execute Handler Failed', "there is no hook named \"" + name + "\""); _a = 0, handlers_1 = handlers; _b.label = 1; case 1: if (!(_a < handlers_1.length)) return [3 /*break*/, 4]; handler = handlers_1[_a]; return [4 /*yield*/, handler.apply(void 0, args)]; case 2: _b.sent(); _b.label = 3; case 3: _a++; return [3 /*break*/, 1]; case 4: return [2 /*return*/]; } }); }); }; App.prototype.registerEventListener = function () { return __awaiter(this, void 0, void 0, function () { var moduleEventListenerMap, _i, _a, eventListener, eventClassModule, eventClass, _loop_1, _b, _c, listenerModuleImport; return __generator(this, function (_d) { switch (_d.label) { case 0: moduleEventListenerMap = []; return [4 /*yield*/, this.executeHookHandlers('butterfly:registerEventListener', moduleEventListenerMap)]; case 1: _d.sent(); _i = 0, _a = __spreadArray(__spreadArray([], moduleEventListenerMap), this.eventListenerMap); _d.label = 2; case 2: if (!(_i < _a.length)) return [3 /*break*/, 8]; eventListener = _a[_i]; return [4 /*yield*/, eventListener.event()]; case 3: eventClassModule = _d.sent(); eventClass = eventClassModule[Object.keys(eventClassModule)[0]]; _loop_1 = function (listenerModuleImport) { var listenerClassModule, listenerClassName, listenerClass, event, listener; return __generator(this, function (_e) { switch (_e.label) { case 0: return [4 /*yield*/, listenerModuleImport()]; case 1: listenerClassModule = _e.sent(); listenerClassName = Object.keys(listenerClassModule)[0]; listenerClass = listenerClassModule[listenerClassName]; event = new eventClass(); listener = new listenerClass(); event_1.Event.on(event.name, function ($event) { listener.handle($event); }); return [2 /*return*/]; } }); }; _b = 0, _c = eventListener.listeners; _d.label = 4; case 4: if (!(_b < _c.length)) return [3 /*break*/, 7]; listenerModuleImport = _c[_b]; return [5 /*yield**/, _loop_1(listenerModuleImport)]; case 5: _d.sent(); _d.label = 6; case 6: _b++; return [3 /*break*/, 4]; case 7: _i++; return [3 /*break*/, 2]; case 8: return [2 /*return*/]; } }); }); }; App.prototype.setup = function () { return __awaiter(this, void 0, void 0, function () { var app; return __generator(this, function (_a) { switch (_a.label) { case 0: app = this.app; app.use(function (req, _res, next) { var start = process.hrtime(); if (!req['locals']) { req['locals'] = { startTime: start, timingMark: {} }; } next(); }); app.disable('x-powered-by'); if (this.useDefaultLogger) { app.use(logger('dev', { skip: function () { return app.get('env') === 'test'; } })); } app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParserGenerator()); if (!this.useViewEngine) return [3 /*break*/, 2]; return [4 /*yield*/, this.executeHookHandlers('butterfly:registerViewPaths', this.viewsPaths)]; case 1: _a.sent(); app.set('view engine', this.viewEngine); app.set('views', this.viewsPaths); _a.label = 2; case 2: return [4 /*yield*/, this.executeHookHandlers('butterfly:setup', app)]; case 3: _a.sent(); return [2 /*return*/]; } }); }); }; App.prototype.connectDatabases = function () { return __awaiter(this, void 0, void 0, function () { var mongoDb, config, redis, promises, _i, _a, database; return __generator(this, function (_b) { switch (_b.label) { case 0: if (this.databaseConfigs.mongo.enable) { mongoDb = new mongodb_1.MongoDb(this.databaseConfigs.mongo, this.app.locals.config.env['NODE_ENV'] !== 'production' && this.app.locals.config.env['NODE_ENV'] !== 'test'); this.databases.push(mongoDb); } if (this.databaseConfigs.redis.enable) { config = this.databaseConfigs.redis; if (config.password === null) delete config.password; redis = new redis_1.Redis(config); this.databases.push(redis); } promises = []; for (_i = 0, _a = this.databases; _i < _a.length; _i++) { database = _a[_i]; promises.push(database.connect()); } return [4 /*yield*/, Promise.all(promises)]; case 1: _b.sent(); return [2 /*return*/]; } }); }); }; App.prototype.registerMiddlewares = function () { return __awaiter(this, void 0, void 0, function () { var moduleMiddlewares, middlewares; return __generator(this, function (_a) { switch (_a.label) { case 0: moduleMiddlewares = []; return [4 /*yield*/, this.executeHookHandlers('butterfly:registerMiddlewares', moduleMiddlewares)]; case 1: _a.sent(); middlewares = __spreadArray([ new middlewares_1.RequestId(), new middlewares_1.ParseAuthUserMiddleware() ], moduleMiddlewares); this.useMiddlewares(middlewares); return [2 /*return*/]; } }); }); }; App.prototype.registerErrorMiddleware = function () { return __awaiter(this, void 0, void 0, function () { var app; var _this = this; return __generator(this, function (_a) { app = this.app; // Catch 404 and forward to error handler app.use(function (req, res, next) { var err = new errors_1.NotFoundError(); res.status(404); next(err); }); // Error handler app.use(function (error, req, res, next) { return __awaiter(_this, void 0, void 0, function () { var isNotProduction, response; return __generator(this, function (_a) { switch (_a.label) { case 0: isNotProduction = req.app.locals.config.env['NODE_ENV'] !== 'production'; response = { status: 500, body: {} }; if (!req['locals']) { req['locals'] = {}; } if (error.config) { response.body = { config: { method: error.config.method, timeout: error.config.timeout, baseURL: error.config.baseURL, url: error.config.url, headers: __assign(__assign({}, (error.config.headers ? error.config.headers : {})), (error.config.headers && error.config.headers.Authorization ? { Authorization: error.config.headers.Authorization.substr(0, 10) + "***" } : {})), data: error.config.data, }, request: error.request ? { method: error.request.method, status: error.request.status, statusText: error.request.statusText, withCredentials: error.request.withCredentials, readyState: error.request.readyState, responseURL: error.request.responseURL, timeout: error.request.timeout, responseType: error.request.responseType, } : null, response: error.response ? { status: error.response.status, headers: error.response.headers, data: error.response.data } : null, }; } else if (error.payload) { response.body = { name: error.name, code: error.code, payload: error.payload }; if (error.payload.status >= 100 && error.payload.status < 600) { response.status = error.payload.status; } } response.body['message'] = error.message; console.error(JSON.stringify({ context: { user: (req['locals'].user ? { id: req['locals'].user.id, username: req['locals'].user.username, role: req['locals'].user.role } : null), req: { originalUrl: req.originalUrl, requestId: req['locals'].requestId, headers: __assign(__assign(__assign({}, req.headers), (req.get('authorization') ? { authorization: req.get('authorization').substr(0, 12) + '***' } : {})), (req.get('cookie') ? { cookie: req.get('cookie').substr(0, 12) + '***' } : {})) }, }, error: error.stack })); return [4 /*yield*/, this.executeHookHandlers('butterfly:onError', { response: response, error: error, req: req })]; case 1: _a.sent(); res.status(response.status); if (this.errorView) { res.render(this.errorView, { error: error, req: req }); } else { res.json(response.body); } return [2 /*return*/]; } }); }); }); return [2 /*return*/]; }); }); }; App.prototype.drawRoutes = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: this.routeDrawer.draw(this.app, this.routes); return [4 /*yield*/, this.executeHookHandlers('butterfly:drawRoutes', route_drawer_1.default, this.app)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; return App; }()); exports.default = App; exports.Butterfly = App;