UNPKG

@topgroup/diginext

Version:

A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.

311 lines (310 loc) 11.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.socketIO = exports.server = exports.app = exports.getIO = exports.setServerStatus = exports.isServerReady = exports.redis = void 0; require("reflect-metadata"); const redis_adapter_1 = require("@socket.io/redis-adapter"); const body_parser_1 = __importDefault(require("body-parser")); const chalk_1 = __importDefault(require("chalk")); const console_1 = __importDefault(require("console")); const cookie_parser_1 = __importDefault(require("cookie-parser")); const cookie_session_1 = __importDefault(require("cookie-session")); const cors_1 = __importDefault(require("cors")); const dayjs_1 = __importDefault(require("dayjs")); const log_1 = require("diginext-utils/dist/xconsole/log"); const express_1 = __importDefault(require("express")); const express_query_parser_1 = require("express-query-parser"); const http_1 = require("http"); const ioredis_1 = require("ioredis"); const morgan_1 = __importDefault(require("morgan")); const passport_1 = __importDefault(require("passport")); const path_1 = __importDefault(require("path")); const rate_limiter_flexible_1 = require("rate-limiter-flexible"); const socket_io_1 = require("socket.io"); const swagger_ui_express_1 = __importDefault(require("swagger-ui-express")); const googleStrategy_1 = require("./modules/passports/googleStrategy"); const jwtStrategy_1 = require("./modules/passports/jwtStrategy"); const app_config_1 = require("./app.config"); const const_1 = require("./config/const"); const failSafeHandler_1 = require("./middlewares/failSafeHandler"); const AppDatabase_1 = __importDefault(require("./modules/AppDatabase")); const startup_scripts_1 = require("./modules/server/startup-scripts"); const basic_auth_1 = __importDefault(require("./routes/api/v1/basic-auth")); const routes_1 = __importDefault(require("./routes/routes")); const SystemLogService_1 = require("./services/SystemLogService"); /** * ENVIRONMENT CONFIG */ const { BASE_PATH, PORT, CLI_MODE } = app_config_1.Config; /** * CORS configuration */ const allowedHosts = [ "localhost:3000", "localhost:6969", "localhost:4000", "localhost:42000", "diginext.site", "www.diginext.site", "topgroup.diginext.site", "app.diginext.site", "*.diginext.site", "topgroup.dxup.dev", "topgroup-v2.dxup.dev", "diginext.vn", "www.diginext.vn", "dxup.dev", "www.dxup.dev", "hobby.dxup.dev", "app.dxup.dev", "*.dxup.dev", "wearetopgroup.com", "digitop.vn", ]; const subdomainWhitelist = /^https?:\/\/(\w+-?\w+\.)*diginext\.site$/; const allowedHeaders = [ "Origin", "X-Requested-With", "x-api-key", "x-auth-cookie", "Content-Type", "Accept", "Authorization", "Cache-Control", "Cookie", "User-Agent", ]; const allowedMethods = ["OPTIONS", "GET", "PATCH", "POST", "DELETE"]; const corsOptions = (req, callback) => { let _corsOptions = { allowedHeaders, methods: allowedMethods }; const host = req.headers.host; if (subdomainWhitelist.test(host) || allowedHosts.includes(host)) { _corsOptions.origin = true; // reflect (enable) the requested origin in the CORS response } else { _corsOptions.origin = false; // disable CORS for this request } // console.log("_corsOptions :>> ", _corsOptions); callback(null, _corsOptions); // callback expects two parameters: error and options }; /** * EXPRESS JS INITIALIZING */ let app; exports.app = app; let server; exports.server = server; let socketIO; exports.socketIO = socketIO; exports.isServerReady = false; function setServerStatus(status) { exports.isServerReady = status; } exports.setServerStatus = setServerStatus; function initialize(db) { // log(`Server is initializing...`); exports.app = app = (0, express_1.default)(); exports.server = server = (0, http_1.createServer)(app); /** * REDIS */ const pubClient = new ioredis_1.Redis({ host: app_config_1.Config.REDIS_HOST, port: app_config_1.Config.REDIS_PORT, password: app_config_1.Config.REDIS_PASSWORD, keyPrefix: `dxup:`, }); const subClient = pubClient.duplicate(); exports.redis = pubClient; pubClient.on("error", (error) => { console_1.default.error("Redis PubClient Error:", error); }); subClient.on("error", (error) => { console_1.default.error("Redis SubClient Error:", error); }); /** * Websocket / SOCKET.IO */ exports.socketIO = socketIO = new socket_io_1.Server(server, { transports: ["websocket"], pingTimeout: 30000, connectTimeout: 90000, upgradeTimeout: 30000, adapter: (0, redis_adapter_1.createAdapter)(pubClient, subClient), }); socketIO.on("connection", (socket) => { // console.log("a user connected"); socket.on("ping", (callback) => { callback(app_config_1.Config.LOCATION); }); socket.on("join", (data) => { // console.log("join room:", data); socket.join(data.room); }); }); /** * CORS MIDDLEWARE */ app.use( // cors({ // // credentials: IsDev() ? false : true, // // allowedOrigins: IsDev() ? "*" : allowedOrigins, // credentials: true, // allowedOrigins, // allowedHeaders, // methods: allowedMethods, // }) (0, cors_1.default)(corsOptions)); // CREDITS app.use((req, res, next) => { res.header("X-Powered-By", "TOP GROUP"); next(); }); /** * SERVING STATIC & UPLOAD FILES */ app.use(express_1.default.static(path_1.default.resolve(const_1.CLI_DIR, "public"))); app.use("/storage", express_1.default.static(path_1.default.resolve(const_1.CLI_DIR, "storage"))); /** * TODO: Enable SWAGGER for API Docs * SWAGGER API DOCS */ app.use("/api-docs", swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup(undefined, { swaggerOptions: { url: "/swagger.json" }, })); /** * PASSPORT STRATEGY */ passport_1.default.use(googleStrategy_1.googleStrategy); passport_1.default.use(jwtStrategy_1.jwtStrategy); passport_1.default.serializeUser((user, done) => done(null, user)); passport_1.default.deserializeUser((obj, done) => done(null, obj)); /** * BODY PARSER */ app.use(body_parser_1.default.urlencoded({ limit: "200mb", extended: true })); app.use(body_parser_1.default.json({ limit: "200mb" })); /** * QUERY PARSER */ app.use((0, express_query_parser_1.queryParser)({ parseNull: true, parseUndefined: true, parseBoolean: true, parseNumber: true, })); /** * COOKIES & SESSION PARSER */ app.use((0, cookie_parser_1.default)()); app.use((0, cookie_session_1.default)({ name: app_config_1.Config.grab(`SESSION_NAME`, `diginext`), secret: app_config_1.Config.grab(`JWT_SECRET`), maxAge: 1000 * 60 * 100, httpOnly: false, secure: !(0, app_config_1.IsDev)() || !(0, app_config_1.IsTest)(), })); /** * AUTHENTICATION MIDDLEWARE */ app.use(passport_1.default.initialize()); app.use(passport_1.default.session()); /** * LOGGING SYSTEM MIDDLEWARE - ENABLED * Enable when running on server */ morgan_1.default.token("user", (req) => (req.user ? `[${req.user.slug}]` : "[unauthenticated]")); morgan_1.default.token("req-headers", (req) => JSON.stringify(req.headers)); const morganMessage = (0, app_config_1.IsDev)() ? "[REQUEST :date[clf]] :method - :user - :url :status :response-time ms - :res[content-length]" : `[REQUEST :date[clf]] :method - :user - ":url HTTP/:http-version" :status :response-time ms :res[content-length] ":referrer" ":user-agent"`; const morganOptions = { skip: (req, res) => { var _a, _b; return req.method.toUpperCase() === "OPTIONS" || ((_a = req.url) === null || _a === void 0 ? void 0 : _a.indexOf("/.well-known")) > -1 || ((_b = req.url) === null || _b === void 0 ? void 0 : _b.indexOf("/api/v1/stats/version")) > -1; }, // write logs to file // stream: accessLogStream, }; if (!(0, app_config_1.IsTest)()) app.use((0, morgan_1.default)(morganMessage, morganOptions)); // Public paths for HEALTHCHECK & Rest APIs: app.use(`/${BASE_PATH}`, routes_1.default); /** * RATE LIMITING MIDDLEWARE */ const rateLimiter = new rate_limiter_flexible_1.RateLimiterMongo({ storeClient: db.connection, tableName: "auth-rate-limit", points: 50, duration: 60, blockDuration: 60 * 60 * 1, // 1 hour }); const authRateLimiterMiddleware = (req, res, next) => { rateLimiter .consume(`${req.ip}-${req.headers["user-agent"]}`) .then(() => { // console.log("req.ip :>> ", req.ip); // console.log("req.headers['user-agent'] :>> ", req.headers["user-agent"]); next(); }) .catch(() => { if (!(0, app_config_1.IsDev)() && !(0, app_config_1.IsTest)()) { res.status(429).send("[BASIC AUTH] Too Many Requests"); } else { next(); } }); }; app.use(`/api/v1`, authRateLimiterMiddleware, basic_auth_1.default); /** * ROUTE 404 & FAIL SAFE HANDLING MIDDLEWARE */ // app.use("*", route404_handler); // make sure the Express app won't be crashed if there are any errors if ((0, app_config_1.IsProd)()) app.use(failSafeHandler_1.failSafeHandler); /** * SERVER HANDLING */ function onConnect() { console_1.default.log(chalk_1.default.green("[SYSTEM]"), `Date & time: ${(0, dayjs_1.default)().format("LLLL")}`); console_1.default.log(chalk_1.default.green("[SYSTEM]"), `✅ Server is UP & listening at port ${PORT}...`); } server.on("error", async (error) => { (0, log_1.logError)(`[FAIL_SAFE_2]`, error); // save log to database // const { SystemLogService } = await import("./services"); const logSvc = new SystemLogService_1.SystemLogService(); logSvc.saveError(error, { name: "server-error" }); }); server.listen(PORT, onConnect); /** * BUILD SERVER INITIAL START-UP SCRIPTS: * - Connect GIT providers (if any) * - Connect container registries (if any) * - Connect K8S clusters (if any) */ (0, startup_scripts_1.startupScripts)().catch((reason) => (0, log_1.logWarn)(reason)); } if (CLI_MODE === "server") { // In your main server file or entry point process.on("unhandledRejection", (reason, promise) => { console_1.default.error("Unhandled Rejection at:", promise, "reason:", reason); // Optional: Add logging or monitoring service }); // log(`Connecting to database. Please wait...`); AppDatabase_1.default.connect(initialize); /** * Close the database connection when the application is terminated */ process.on("SIGINT", async () => { await AppDatabase_1.default.disconnect(); process.exit(0); }); } const getIO = () => socketIO; exports.getIO = getIO;