@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
311 lines (310 loc) • 11.5 kB
JavaScript
"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;