process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; import { toNodeHandler } from "better-auth/node"; import cors from "cors"; import express from "express"; import { createServer } from "http"; import { createProxyMiddleware } from "http-proxy-middleware"; import morgan from "morgan"; import os from "os"; import { dirname, join } from "path"; import swaggerJsdoc from "swagger-jsdoc"; import swaggerUi from "swagger-ui-express"; import { fileURLToPath } from "url"; import { schedulerManager } from "./src/internal/logistics/controller/schedulerManager.js"; import { setupMobileRoutes } from "./src/internal/mobile/route.js"; import { printers } from "./src/internal/ocp/printers/printers.js"; import { setupRoutes } from "./src/internal/routerHandler/routeHandler.js"; import { baseModules } from "./src/internal/system/controller/modules/baseModules.js"; import { baseSettings } from "./src/internal/system/controller/settings/baseSettings.js"; import { addListeners, manualFixes, settingsMigrate, } from "./src/internal/system/utlis/addListeners.js"; import { swaggerOptions } from "./src/pkg/apiDocs/swaggerOptions.js"; import { auth } from "./src/pkg/auth/auth.js"; import { db } from "./src/pkg/db/db.js"; import { settings } from "./src/pkg/db/schema/settings.js"; import { createLogger } from "./src/pkg/logger/logger.js"; import { v1Listener } from "./src/pkg/logger/v1Listener.js"; import { apiHitMiddleware } from "./src/pkg/middleware/apiHits.js"; import { initializeProdPool, pool } from "./src/pkg/prodSql/prodSqlConnect.js"; import { validateEnv } from "./src/pkg/utils/envValidator.js"; import { sendNotify } from "./src/pkg/utils/notify.js"; import { returnFunc } from "./src/pkg/utils/return.js"; import { tryCatch } from "./src/pkg/utils/tryCatch.js"; import { setupIoServer } from "./src/ws/server.js"; const main = async () => { const env = validateEnv(process.env); const PORT = Number(process.env.VITE_PORT) || 4200; //create the logger const log = createLogger({ module: "system", subModule: "main start" }); // base path let basePath: string = ""; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Db connection stuff const res = await tryCatch(db.select().from(settings)); if (res.error) { return returnFunc({ success: false, module: "system", level: "fatal", message: `Database lookup failed`, notify: false, data: [], }); } if (res.data.length === 0) { //return // returnFunc({ // success: false, // module: "system", // level: "fatal", // message: `This seems to be the first time you have started the app please validate the settings have been intiated`, // notify: false, // data: [], // }); } // connect to the prod sql console.log("Connecting to the sql server"); await initializeProdPool(); // express app const app = express(); // global env that run only in dev if (process.env.NODE_ENV?.trim() !== "production") { app.use(morgan("tiny")); basePath = "/lst"; app.use( basePath + "/test", express.static(join(__dirname, "../controller")), ); } // old app prox temp stuff app.use( basePath + "/old", createProxyMiddleware({ target: `http://localhost:${process.env.V1PORT || "3000"}`, // change this to pull from the correct port changeOrigin: true, pathRewrite: (path, req) => { // Remove the basePath + '/old' prefix from the path dynamically return path.replace(`${basePath}/old`, ""); }, headers: { // forward auth headers if needed "X-Forwarded-By": "express-proxy", }, }), ); // global middleware app.set("trust proxy", true); app.use(apiHitMiddleware); app.all(basePath + "/api/auth/*splat", toNodeHandler(auth)); // sign-in sign-out app.use(express.json()); const allowedOrigins = [ /^https?:\/\/localhost:(5173|5500|4200|3000|4000)$/, // all the allowed backend ports /^http?:\/\/localhost:(5173|5500|4200|3000|4000)$/, /^https?:\/\/.*\.alpla\.net$/, "http://localhost:4173", "http://localhost:4200", "http://localhost:3000", "http://localhost:3001", "http://localhost:4000", "http://localhost:4001", "http://localhost:5500", env.BETTER_AUTH_URL, // prod ]; app.use( cors({ origin: (origin, callback) => { //console.log("CORS request from origin:", origin); if (!origin) return callback(null, true); // allow same-site or direct calls try { const hostname = new URL(origin).hostname; // strips protocol/port //console.log("Parsed hostname:", hostname); if (allowedOrigins.includes(origin)) { return callback(null, true); } // Now this works for *.alpla.net if (hostname.endsWith(".alpla.net") || hostname === "alpla.net") { return callback(null, true); } } catch (err) { //console.error("Invalid Origin header:", origin); } return callback(new Error("Not allowed by CORS: " + origin)); }, methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], credentials: true, exposedHeaders: [ "set-cookie", "expo-protocol-version", "expo-sfv-version", ], allowedHeaders: [ "Content-Type", "Authorization", "X-Requested-With", "XMLHttpRequest", "expo-runtime-version", "expo-platform", "expo-channel-name", "*", ], }), ); // docs and routes const openapiSpec: any = swaggerJsdoc(swaggerOptions); app.use( basePath + "/api/docs", swaggerUi.serve, swaggerUi.setup(openapiSpec), ); app.use(basePath + "/d", express.static(join(__dirname, "../lstDocs/build"))); app.use( basePath + "/app", express.static(join(__dirname, "../frontend/dist")), ); app.get(basePath + "/app/*splat", (req, res) => { res.sendFile(join(__dirname, "../frontend/dist/index.html")); }); app.get(basePath + "/d/*splat", (req, res) => { res.sendFile(join(__dirname, "../lstDocs/build/index.html")); }); // server setup const server = createServer(app); // register app setupRoutes(app, basePath); // ws stuff setupIoServer(server, basePath); // start all systems after we are intiallally up and running setTimeout(() => { baseSettings(); baseModules(); printers(); schedulerManager(); // start up the v1listener v1Listener(); addListeners(); //userMigrate(); // some temp fixes // above 230 remove these manualFixes(); settingsMigrate(); }, 5 * 1000); // setTimeout(() => { // startHonoServer(); // }, 8 * 1000); // start the server up server.listen(PORT, "0.0.0.0", () => log.info( { stack: { name: "test" } }, `Server running in ${ process.env.NODE_ENV ? process.env.NODE_ENV : "dev" }, on http://0.0.0.0:${PORT}${basePath}`, ), ); process.on("uncaughtException", async (err) => { //console.log("Uncaught Exception:", err); // await closePool(); // const emailData = { // email: "blake.matthes@alpla.com", // should be moved to the db so it can be reused. // subject: `${os.hostname()} has just encountered a crash.`, // template: "serverCrash", // context: { // error: err, // plant: `${os.hostname()}`, // }, // }; if (!process.env.WEBHOOK_URL) { // await sendEmail(emailData); } else { log.fatal({ stack: err.stack }, err.message); await sendNotify({ module: "system", subModule: "fatalCrash", hostname: os.hostname(), message: err.message, stack: err?.stack, }); } //process.exit(1); }); process.on("SIGINT", async () => { console.log("\nGracefully shutting down..."); try { await pool.close(); console.log("Closed SQL connection."); } catch (err) { console.error("Error closing SQL connection:", err); } finally { process.exit(0); } }); // Also handle other termination signals (optional) process.on("SIGTERM", async () => { console.log("SIGTERM received. Closing SQL connection..."); try { await pool.close(); } catch (err) { console.error(err); } finally { process.exit(0); } }); // setInterval(() => { // const used = process.memoryUsage(); // console.log( // `Heap: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB / RSS: ${( // used.rss / 1024 / 1024 // ).toFixed(2)} MB`, // ); // }, 10000); }; main();