From a09ad8773c77b7b23ce98b3b3f6ce6122842f3ff Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Thu, 16 Oct 2025 14:36:12 -0500 Subject: [PATCH] feat(settings): added in settings --- .vscode/settings.json | 16 +- app/main.ts | 315 +++++++++--------- .../admin/controller/servers/matchServers.ts | 22 ++ app/src/internal/admin/routes.ts | 7 +- .../admin/routes/servers/addServer.ts | 170 +++++----- .../admin/routes/servers/updateServer.ts | 220 ++++++------ .../controller/settings/baseSettings.ts | 37 ++ .../system/controller/settings/settings.json | 16 + app/src/internal/system/routes.ts | 8 +- .../system/routes/settings/getSettings.ts | 35 ++ .../system/routes/settings/settingRoutes.ts | 11 + .../system/routes/settings/updateSetting.ts | 141 ++++++++ app/src/pkg/db/schema/servers.ts | 76 +++-- app/src/pkg/utils/delay.ts | 3 + 14 files changed, 684 insertions(+), 393 deletions(-) create mode 100644 app/src/internal/admin/controller/servers/matchServers.ts create mode 100644 app/src/internal/system/controller/settings/baseSettings.ts create mode 100644 app/src/internal/system/controller/settings/settings.json create mode 100644 app/src/internal/system/routes/settings/getSettings.ts create mode 100644 app/src/internal/system/routes/settings/settingRoutes.ts create mode 100644 app/src/internal/system/routes/settings/updateSetting.ts create mode 100644 app/src/pkg/utils/delay.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d78bd7..dac31fb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,11 @@ { - "editor.defaultFormatter": "biomejs.biome", - "workbench.colorTheme": "Default Dark+", - "terminal.integrated.env.windows": {}, - "editor.formatOnSave": true, - "editor.codeActionsOnSave": {"source.fixAll.biome": "explicit", -"source.organizeImports.biome": "explicit" }, - "cSpell.words": ["alpla", "alplamart", "alplaprod", "ppoo"] + "editor.defaultFormatter": "biomejs.biome", + "workbench.colorTheme": "Default Dark+", + "terminal.integrated.env.windows": {}, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, + "cSpell.words": ["alpla", "alplamart", "alplaprod", "intiallally", "ppoo"] } diff --git a/app/main.ts b/app/main.ts index eda5a9d..fcd6e5a 100644 --- a/app/main.ts +++ b/app/main.ts @@ -1,200 +1,201 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +import { toNodeHandler } from "better-auth/node"; +import cors from "cors"; import express from "express"; -import morgan from "morgan"; import { createServer } from "http"; -import { setupRoutes } from "./src/internal/routerHandler/routeHandler.js"; -import { printers } from "./src/internal/ocp/printers/printers.js"; +import morgan from "morgan"; +import os from "os"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; +import { schedulerManager } from "./src/internal/logistics/controller/schedulerManager.js"; +import { printers } from "./src/internal/ocp/printers/printers.js"; +import { setupRoutes } from "./src/internal/routerHandler/routeHandler.js"; +import { baseSettings } from "./src/internal/system/controller/settings/baseSettings.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 { validateEnv } from "./src/pkg/utils/envValidator.js"; import { createLogger } from "./src/pkg/logger/logger.js"; -import { returnFunc } from "./src/pkg/utils/return.js"; -import { initializeProdPool } from "./src/pkg/prodSql/prodSqlConnect.js"; -import { tryCatch } from "./src/pkg/utils/tryCatch.js"; -import os from "os"; -import cors from "cors"; -import { sendNotify } from "./src/pkg/utils/notify.js"; -import { toNodeHandler } from "better-auth/node"; -import { auth } from "./src/pkg/auth/auth.js"; +import { v1Listener } from "./src/pkg/logger/v1Listener.js"; import { apiHitMiddleware } from "./src/pkg/middleware/apiHits.js"; +import { initializeProdPool } 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"; -import { schedulerManager } from "./src/internal/logistics/controller/schedulerManager.js"; const main = async () => { - const env = validateEnv(process.env); - const PORT = Number(env.VITE_PORT) || 4200; + const env = validateEnv(process.env); + const PORT = Number(env.VITE_PORT) || 4200; - //create the logger - const log = createLogger({ module: "system", subModule: "main start" }); + //create the logger + const log = createLogger({ module: "system", subModule: "main start" }); - // base path - let basePath: string = ""; + // base path + let basePath: string = ""; - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); - // Db connection stuff - const res = await tryCatch(db.select().from(settings)); + // 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.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: [], - // }); - } + 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 - await initializeProdPool(); + // connect to the prod sql + await initializeProdPool(); - // express app - const app = express(); + // 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"; + // 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")) - ); - } + app.use( + basePath + "/test", + express.static(join(__dirname, "../controller")), + ); + } - // 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()); + // 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 - /^https?:\/\/.*\.alpla\.net$/, - env.BETTER_AUTH_URL, // prod - ]; + const allowedOrigins = [ + /^https?:\/\/localhost:(5173|5500|4200|3000|4000)$/, // all the allowed backend ports + /^https?:\/\/.*\.alpla\.net$/, + env.BETTER_AUTH_URL, // prod + ]; - app.use( - cors({ - origin: (origin, callback) => { - //console.log("CORS request from origin:", origin); + 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 + 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); + try { + const hostname = new URL(origin).hostname; // strips protocol/port + //console.log("Parsed hostname:", hostname); - if (allowedOrigins.includes(origin)) { - return callback(null, true); - } + 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); - } + // 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, - }) - ); + return callback(new Error("Not allowed by CORS: " + origin)); + }, + methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], + credentials: true, + }), + ); - // docs and api stuff - app.use( - basePath + "/d", - express.static(join(__dirname, "../lstDocs/build")) - ); - app.use( - basePath + "/app", - express.static(join(__dirname, "../frontend/dist")) - ); + // docs and api stuff + app.use(basePath + "/d", express.static(join(__dirname, "../lstDocs/build"))); + app.use( + basePath + "/app", + express.static(join(__dirname, "../frontend/dist")), + ); - // server setup - const server = createServer(app); + // server setup + const server = createServer(app); - // register app - setupRoutes(app, basePath); + // register app + setupRoutes(app, basePath); - // ws stuff - setupIoServer(server, basePath); + // ws stuff + setupIoServer(server, basePath); - // sub systems - printers(); - schedulerManager(); + // start all systems after we are intiallally up and running + setTimeout(() => { + baseSettings(); + printers(); + schedulerManager(); - // 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}` - ) - ); + // start up the v1listener + v1Listener(); + }, 5 * 1000); - 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()}`, - // }, - // }; + // 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}`, + ), + ); - 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.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()}`, + // }, + // }; - //process.exit(1); - }); + 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, + }); + } - // setInterval(() => { - // const used = process.memoryUsage(); - // console.log( - // `Heap: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB / RSS: ${( - // used.rss / - // 1024 / - // 1024 - // ).toFixed(2)} MB` - // ); - // }, 10000); + //process.exit(1); + }); + + // 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(); diff --git a/app/src/internal/admin/controller/servers/matchServers.ts b/app/src/internal/admin/controller/servers/matchServers.ts new file mode 100644 index 0000000..ccbd5bd --- /dev/null +++ b/app/src/internal/admin/controller/servers/matchServers.ts @@ -0,0 +1,22 @@ +/** + * This is intended for when running as dev so we can always keep the servers in sync with the main server. + * in the event the server has a change on it we want to make sure we stay in sync + */ + +import { createLogger } from "../../../../pkg/logger/logger.js"; + +export const mainServerSync = async () => { + const log = createLogger({ module: "admin", subModule: "main server sync" }); + if ( + process.env.NODE_ENV?.trim() !== "production" && + process.env.MAIN_SERVER + ) { + log.info( + {}, + "Running in dev and have a main server set we will now pull the servers and look for any changes", + ); + } else { + log.info({}, "This server is running in production no sync will happen"); + return; + } +}; diff --git a/app/src/internal/admin/routes.ts b/app/src/internal/admin/routes.ts index 4386acd..19f5946 100644 --- a/app/src/internal/admin/routes.ts +++ b/app/src/internal/admin/routes.ts @@ -1,6 +1,6 @@ import type { Express, Request, Response } from "express"; import { requireAuth } from "../../pkg/middleware/authMiddleware.js"; - +import { mainServerSync } from "./controller/servers/matchServers.js"; //admin routes import users from "./routes/getUserRoles.js"; import grantRoles from "./routes/grantRole.js"; @@ -30,4 +30,9 @@ export const setupAdminRoutes = (app: Express, basePath: string) => { requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this revokeRoles, ); + + // run the sync only on startup + setTimeout(() => { + mainServerSync(); + }, 5 * 1000); }; diff --git a/app/src/internal/admin/routes/servers/addServer.ts b/app/src/internal/admin/routes/servers/addServer.ts index ff43e98..8d16d20 100644 --- a/app/src/internal/admin/routes/servers/addServer.ts +++ b/app/src/internal/admin/routes/servers/addServer.ts @@ -1,100 +1,106 @@ -import { Router } from "express"; -import type { Request, Response } from "express"; -import { - insertServerDataSchema, - serverData, -} from "../../../../pkg/db/schema/servers.js"; -import { db } from "../../../../pkg/db/db.js"; -import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; -import type { DrizzleError } from "drizzle-orm"; import axios from "axios"; -import { createLogger } from "../../../../pkg/logger/logger.js"; +import { type DrizzleError, sql } from "drizzle-orm"; +import type { Request, Response } from "express"; +import { Router } from "express"; import https from "https"; +import { db } from "../../../../pkg/db/db.js"; +import { + insertServerDataSchema, + serverData, +} from "../../../../pkg/db/schema/servers.js"; +import { createLogger } from "../../../../pkg/logger/logger.js"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; const router = Router(); router.post("/", async (req: Request, res: Response) => { - // when a new server is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there - //res.status(200).json({ message: "Server added", ip: req.hostname }); - const log = createLogger({ module: "admin", subModule: "add server" }); - const parsed = insertServerDataSchema.safeParse(req.body); + // when a new server is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there + //res.status(200).json({ message: "Server added", ip: req.hostname }); + const log = createLogger({ module: "admin", subModule: "add server" }); + const parsed = insertServerDataSchema.safeParse(req.body); - if (!parsed.success) { - return res.status(400).json({ errors: parsed.error.flatten() }); - } + if (!parsed.success) { + return res.status(400).json({ errors: parsed.error.flatten() }); + } - const { data, error } = await tryCatch( - db - .insert(serverData) - .values(parsed.data) - //.onConflictDoNothing() - .returning({ - name: serverData.name, - plantToken: serverData.plantToken, - }) - ); + const { data, error } = await tryCatch( + db + .insert(serverData) + .values({ + ...parsed.data, + add_user: req.user?.username, + add_date: sql`NOW()`, + upd_user: req.user?.username, + upd_date: sql`NOW()`, + }) + //.onConflictDoNothing() + .returning({ + name: serverData.name, + plantToken: serverData.plantToken, + }), + ); - if (error) { - const err: DrizzleError = error; - return res.status(400).json({ - message: `Error adding the server`, - error: err.cause, - }); - } + if (error) { + const err: DrizzleError = error; + return res.status(400).json({ + message: `Error adding the server`, + error: err.cause, + }); + } - if (req.hostname === "localhost" && process.env.MAIN_SERVER) { - log.info({}, "Running in dev server about to add in a new server"); - const axiosInstance = axios.create({ - httpsAgent: new https.Agent({ rejectUnauthorized: false }), - baseURL: process.env.MAIN_SERVER, // e.g. "https://example.com" - withCredentials: true, - }); + if (req.hostname === "localhost" && process.env.MAIN_SERVER) { + log.info({}, "Running in dev server about to add in a new server"); + const axiosInstance = axios.create({ + httpsAgent: new https.Agent({ rejectUnauthorized: false }), + baseURL: process.env.MAIN_SERVER, // e.g. "https://example.com" + withCredentials: true, + }); - const loginRes = (await axiosInstance.post( - `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`, - { - username: process.env.MAIN_SERVER_USERNAME, - password: process.env.MAIN_SERVER_PASSWORD, - }, - { - headers: { "Content-Type": "application/json" }, - } - )) as any; - const setCookie = loginRes.headers["set-cookie"][0]; + const loginRes = (await axiosInstance.post( + `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`, + { + username: process.env.MAIN_SERVER_USERNAME, + password: process.env.MAIN_SERVER_PASSWORD, + }, + { + headers: { "Content-Type": "application/json" }, + }, + )) as any; + const setCookie = loginRes.headers["set-cookie"][0]; - if (!setCookie) { - throw new Error("Did not receive a Set-Cookie header from login"); - } + if (!setCookie) { + throw new Error("Did not receive a Set-Cookie header from login"); + } - const { data, error } = await tryCatch( - axios.post( - `${process.env.MAIN_SERVER}/lst/api/admin/server`, - parsed.data, - { - headers: { - "Content-Type": "application/json", - Cookie: setCookie.split(";")[0], - }, - withCredentials: true, - } - ) - ); + const { data, error } = await tryCatch( + axios.post( + `${process.env.MAIN_SERVER}/lst/api/admin/server`, + parsed.data, + { + headers: { + "Content-Type": "application/json", + Cookie: setCookie.split(";")[0], + }, + withCredentials: true, + }, + ), + ); - if (error) { - log.error( - { stack: error }, - "There was an error adding the server to Main Server" - ); - } - log.info( - { stack: data?.data }, - "A new Server was just added to the server." - ); - } + if (error) { + log.error( + { stack: error }, + "There was an error adding the server to Main Server", + ); + } + log.info( + { stack: data?.data }, + "A new Server was just added to the server.", + ); + } - return res - .status(201) - .json({ message: `Server ${data[0]?.name} added`, data: data }); + return res + .status(201) + .json({ message: `Server ${data[0]?.name} added`, data: data }); }); export default router; diff --git a/app/src/internal/admin/routes/servers/updateServer.ts b/app/src/internal/admin/routes/servers/updateServer.ts index 74e27f7..335c908 100644 --- a/app/src/internal/admin/routes/servers/updateServer.ts +++ b/app/src/internal/admin/routes/servers/updateServer.ts @@ -1,139 +1,141 @@ -import { Router } from "express"; +import axios from "axios"; +import { eq, sql } from "drizzle-orm"; import type { Request, Response } from "express"; +import { Router } from "express"; +import https from "https"; import { db } from "../../../../pkg/db/db.js"; import { serverData } from "../../../../pkg/db/schema/servers.js"; -import { eq } from "drizzle-orm"; -import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; -import axios from "axios"; import { createLogger } from "../../../../pkg/logger/logger.js"; -import https from "https"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; const router = Router(); router.patch("/:token", async (req: Request, res: Response) => { - const log = createLogger({ module: "admin", subModule: "update server" }); + const log = createLogger({ module: "admin", subModule: "update server" }); - // when a server is updated and is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there, we want to insert with update on conflict. - const token = req.params.token; - const updates: Record = {}; + // when a server is updated and is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there, we want to insert with update on conflict. + const token = req.params.token; + const updates: Record = {}; - if (req.body?.name !== undefined) { - updates.name = req.body.name; - } - if (req.body?.serverDNS !== undefined) { - updates.serverDNS = req.body.serverDNS; - } - if (req.body?.ipAddress !== undefined) { - updates.ipAddress = req.body.ipAddress; - } + if (req.body?.name !== undefined) { + updates.name = req.body.name; + } + if (req.body?.serverDNS !== undefined) { + updates.serverDNS = req.body.serverDNS; + } + if (req.body?.ipAddress !== undefined) { + updates.ipAddress = req.body.ipAddress; + } - if (req.body?.greatPlainsPlantCode !== undefined) { - updates.greatPlainsPlantCode = req.body.greatPlainsPlantCode; - } + if (req.body?.greatPlainsPlantCode !== undefined) { + updates.greatPlainsPlantCode = req.body.greatPlainsPlantCode; + } - if (req.body?.lstServerPort !== undefined) { - updates.lstServerPort = req.body.lstServerPort; - } + if (req.body?.lstServerPort !== undefined) { + updates.lstServerPort = req.body.lstServerPort; + } - if (req.body?.serverLoc !== undefined) { - updates.serverLoc = req.body.serverLoc; - } + if (req.body?.serverLoc !== undefined) { + updates.serverLoc = req.body.serverLoc; + } - if (req.body?.streetAddress !== undefined) { - updates.streetAddress = req.body.streetAddress; - } + if (req.body?.streetAddress !== undefined) { + updates.streetAddress = req.body.streetAddress; + } - if (req.body?.cityState !== undefined) { - updates.cityState = req.body.cityState; - } + if (req.body?.cityState !== undefined) { + updates.cityState = req.body.cityState; + } - if (req.body?.zipcode !== undefined) { - updates.zipcode = req.body.zipcode; - } + if (req.body?.zipcode !== undefined) { + updates.zipcode = req.body.zipcode; + } - if (req.body?.contactEmail !== undefined) { - updates.contactEmail = req.body.contactEmail; - } + if (req.body?.contactEmail !== undefined) { + updates.contactEmail = req.body.contactEmail; + } - if (req.body?.contactPhone !== undefined) { - updates.contactPhone = req.body.contactPhone; - } + if (req.body?.contactPhone !== undefined) { + updates.contactPhone = req.body.contactPhone; + } - if (req.body?.customerTiAcc !== undefined) { - updates.customerTiAcc = req.body.customerTiAcc; - } + if (req.body?.customerTiAcc !== undefined) { + updates.customerTiAcc = req.body.customerTiAcc; + } - if (req.body?.active !== undefined) { - updates.active = req.body.active; - } - try { - if (Object.keys(updates).length > 0) { - await db - .update(serverData) - .set(updates) - .where(eq(serverData.plantToken, token)); - } + if (req.body?.active !== undefined) { + updates.active = req.body.active; + } - if (req.hostname === "localhost" && process.env.MAIN_SERVER) { - log.info({}, "Running in dev server about to add in a new server"); - const axiosInstance = axios.create({ - httpsAgent: new https.Agent({ rejectUnauthorized: false }), - baseURL: process.env.MAIN_SERVER, - withCredentials: true, - }); + updates.upd_user = req.user!.username || "lst_user"; + updates.upd_date = sql`NOW()`; - const loginRes = (await axiosInstance.post( - `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`, - { - username: process.env.MAIN_SERVER_USERNAME, - password: process.env.MAIN_SERVER_PASSWORD, - }, - { - headers: { "Content-Type": "application/json" }, - } - )) as any; + try { + if (Object.keys(updates).length > 0) { + await db + .update(serverData) + .set(updates) + .where(eq(serverData.plantToken, token)); + } - const setCookie = loginRes?.headers["set-cookie"][0]; + if (req.hostname === "localhost" && process.env.MAIN_SERVER) { + log.info({}, "Running in dev server about to add in a new server"); + const axiosInstance = axios.create({ + httpsAgent: new https.Agent({ rejectUnauthorized: false }), + baseURL: process.env.MAIN_SERVER, + withCredentials: true, + }); - //console.log(setCookie.split(";")[0].replace("__Secure-", "")); + const loginRes = (await axiosInstance.post( + `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`, + { + username: process.env.MAIN_SERVER_USERNAME, + password: process.env.MAIN_SERVER_PASSWORD, + }, + { + headers: { "Content-Type": "application/json" }, + }, + )) as any; - if (!setCookie) { - throw new Error( - "Did not receive a Set-Cookie header from login" - ); - } + const setCookie = loginRes?.headers["set-cookie"][0]; - const { data, error } = await tryCatch( - axios.patch( - `${process.env.MAIN_SERVER}/lst/api/admin/server/${token}`, - updates, - { - headers: { - "Content-Type": "application/json", - Cookie: setCookie.split(";")[0], - }, - withCredentials: true, - } - ) - ); + //console.log(setCookie.split(";")[0].replace("__Secure-", "")); - if (error) { - console.log(error); - log.error( - { stack: error }, - "There was an error adding the server to Main Server" - ); - } - log.info( - { stack: data?.data }, - "A new Server was just added to the server." - ); - } - res.status(200).json({ message: `${token} Server was just updated` }); - } catch (error) { - console.log(error); - res.status(400).json({ message: "Error Server updated", error }); - } + if (!setCookie) { + throw new Error("Did not receive a Set-Cookie header from login"); + } + + const { data, error } = await tryCatch( + axios.patch( + `${process.env.MAIN_SERVER}/lst/api/admin/server/${token}`, + updates, + { + headers: { + "Content-Type": "application/json", + Cookie: setCookie.split(";")[0], + }, + withCredentials: true, + }, + ), + ); + + if (error) { + console.log(error); + log.error( + { stack: error }, + "There was an error adding the server to Main Server", + ); + } + log.info( + { stack: data?.data }, + "A new Server was just added to the server.", + ); + } + res.status(200).json({ message: `${token} Server was just updated` }); + } catch (error) { + console.log(error); + res.status(400).json({ message: "Error Server updated", error }); + } }); export default router; diff --git a/app/src/internal/system/controller/settings/baseSettings.ts b/app/src/internal/system/controller/settings/baseSettings.ts new file mode 100644 index 0000000..3b322b1 --- /dev/null +++ b/app/src/internal/system/controller/settings/baseSettings.ts @@ -0,0 +1,37 @@ +/** + * will be all the base settings so we dont have to remember to add ever new setting in these will be the defaults + */ + +import { readFileSync } from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { db } from "../../../../pkg/db/db.js"; +import { settings } from "../../../../pkg/db/schema/settings.js"; +import { createLogger } from "../../../../pkg/logger/logger.js"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export const baseSettings = async () => { + const log = createLogger({ module: "system", subModule: "base settings" }); + const settingsPath = path.resolve(__dirname, "./settings.json"); + + const newSettings = JSON.parse(readFileSync(settingsPath, "utf-8")); + + const { data, error } = await tryCatch( + db + .insert(settings) + .values(newSettings) + .onConflictDoNothing() + .returning({ name: settings.name }), + ); + + if (error) { + log.error({ error }, "There was an error adding new settings"); + } + + if (data) { + log.info({ newSettingsAdded: data }, "New settings added"); + } +}; diff --git a/app/src/internal/system/controller/settings/settings.json b/app/src/internal/system/controller/settings/settings.json new file mode 100644 index 0000000..fb73115 --- /dev/null +++ b/app/src/internal/system/controller/settings/settings.json @@ -0,0 +1,16 @@ +[ + { + "name": "plantToken", + "value": "test3", + "description": "The plant token for the plant IE: test3 or usday1", + "moduleName": "system", + "roles": ["systemAdmin"] + }, + { + "name": "dbServer", + "value": "usmcd1vms036", + "description": "What is the db server", + "moduleName": "system", + "roles": ["systemAdmin"] + } +] diff --git a/app/src/internal/system/routes.ts b/app/src/internal/system/routes.ts index 0bbd8fa..db41aff 100644 --- a/app/src/internal/system/routes.ts +++ b/app/src/internal/system/routes.ts @@ -1,6 +1,12 @@ import type { Express, Request, Response } from "express"; +import settings from "./routes/settings/settingRoutes.js"; import stats from "./routes/stats.js"; export const setupSystemRoutes = (app: Express, basePath: string) => { - app.use(basePath + "/api/system/stats", stats); + app.use(basePath + "/api/system/stats", stats); + + app.use( + basePath + "/api/system/settings", // will pass bc system admin but this is just telling us we need this + settings, + ); }; diff --git a/app/src/internal/system/routes/settings/getSettings.ts b/app/src/internal/system/routes/settings/getSettings.ts new file mode 100644 index 0000000..80f646b --- /dev/null +++ b/app/src/internal/system/routes/settings/getSettings.ts @@ -0,0 +1,35 @@ +import { Router } from "express"; +import type { Request, Response } from "express"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; +import { db } from "../../../../pkg/db/db.js"; +import { serverData } from "../../../../pkg/db/schema/servers.js"; +import { and, asc, eq } from "drizzle-orm"; + +const router = Router(); + +router.get("/", async (req: Request, res: Response) => { + const token = req.query.token; + + const conditions = []; + + if (token !== undefined) { + conditions.push(eq(serverData.plantToken, `${token}`)); + } + + conditions.push(eq(serverData.active, true)); + + const { data, error } = await tryCatch( + db + .select() + .from(serverData) + .where(and(...conditions)) + .orderBy(asc(serverData.name)) + ); + + if (error) { + return res.status(400).json({ error: error }); + } + res.status(200).json({ message: "Current Active server", data: data }); +}); + +export default router; diff --git a/app/src/internal/system/routes/settings/settingRoutes.ts b/app/src/internal/system/routes/settings/settingRoutes.ts new file mode 100644 index 0000000..f832639 --- /dev/null +++ b/app/src/internal/system/routes/settings/settingRoutes.ts @@ -0,0 +1,11 @@ +import { Router } from "express"; +import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js"; +import getSettings from "./getSettings.js"; +import updateSetting from "./updateSetting.js"; + +const router = Router(); + +router.use("/", getSettings); +router.use("/", requireAuth("system", ["systemAdmin", "admin"]), updateSetting); + +export default router; diff --git a/app/src/internal/system/routes/settings/updateSetting.ts b/app/src/internal/system/routes/settings/updateSetting.ts new file mode 100644 index 0000000..335c908 --- /dev/null +++ b/app/src/internal/system/routes/settings/updateSetting.ts @@ -0,0 +1,141 @@ +import axios from "axios"; +import { eq, sql } from "drizzle-orm"; +import type { Request, Response } from "express"; +import { Router } from "express"; +import https from "https"; +import { db } from "../../../../pkg/db/db.js"; +import { serverData } from "../../../../pkg/db/schema/servers.js"; +import { createLogger } from "../../../../pkg/logger/logger.js"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; + +const router = Router(); + +router.patch("/:token", async (req: Request, res: Response) => { + const log = createLogger({ module: "admin", subModule: "update server" }); + + // when a server is updated and is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there, we want to insert with update on conflict. + const token = req.params.token; + const updates: Record = {}; + + if (req.body?.name !== undefined) { + updates.name = req.body.name; + } + if (req.body?.serverDNS !== undefined) { + updates.serverDNS = req.body.serverDNS; + } + if (req.body?.ipAddress !== undefined) { + updates.ipAddress = req.body.ipAddress; + } + + if (req.body?.greatPlainsPlantCode !== undefined) { + updates.greatPlainsPlantCode = req.body.greatPlainsPlantCode; + } + + if (req.body?.lstServerPort !== undefined) { + updates.lstServerPort = req.body.lstServerPort; + } + + if (req.body?.serverLoc !== undefined) { + updates.serverLoc = req.body.serverLoc; + } + + if (req.body?.streetAddress !== undefined) { + updates.streetAddress = req.body.streetAddress; + } + + if (req.body?.cityState !== undefined) { + updates.cityState = req.body.cityState; + } + + if (req.body?.zipcode !== undefined) { + updates.zipcode = req.body.zipcode; + } + + if (req.body?.contactEmail !== undefined) { + updates.contactEmail = req.body.contactEmail; + } + + if (req.body?.contactPhone !== undefined) { + updates.contactPhone = req.body.contactPhone; + } + + if (req.body?.customerTiAcc !== undefined) { + updates.customerTiAcc = req.body.customerTiAcc; + } + + if (req.body?.active !== undefined) { + updates.active = req.body.active; + } + + updates.upd_user = req.user!.username || "lst_user"; + updates.upd_date = sql`NOW()`; + + try { + if (Object.keys(updates).length > 0) { + await db + .update(serverData) + .set(updates) + .where(eq(serverData.plantToken, token)); + } + + if (req.hostname === "localhost" && process.env.MAIN_SERVER) { + log.info({}, "Running in dev server about to add in a new server"); + const axiosInstance = axios.create({ + httpsAgent: new https.Agent({ rejectUnauthorized: false }), + baseURL: process.env.MAIN_SERVER, + withCredentials: true, + }); + + const loginRes = (await axiosInstance.post( + `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`, + { + username: process.env.MAIN_SERVER_USERNAME, + password: process.env.MAIN_SERVER_PASSWORD, + }, + { + headers: { "Content-Type": "application/json" }, + }, + )) as any; + + const setCookie = loginRes?.headers["set-cookie"][0]; + + //console.log(setCookie.split(";")[0].replace("__Secure-", "")); + + if (!setCookie) { + throw new Error("Did not receive a Set-Cookie header from login"); + } + + const { data, error } = await tryCatch( + axios.patch( + `${process.env.MAIN_SERVER}/lst/api/admin/server/${token}`, + updates, + { + headers: { + "Content-Type": "application/json", + Cookie: setCookie.split(";")[0], + }, + withCredentials: true, + }, + ), + ); + + if (error) { + console.log(error); + log.error( + { stack: error }, + "There was an error adding the server to Main Server", + ); + } + log.info( + { stack: data?.data }, + "A new Server was just added to the server.", + ); + } + res.status(200).json({ message: `${token} Server was just updated` }); + } catch (error) { + console.log(error); + res.status(400).json({ message: "Error Server updated", error }); + } +}); + +export default router; diff --git a/app/src/pkg/db/schema/servers.ts b/app/src/pkg/db/schema/servers.ts index 5c5d39e..57e4743 100644 --- a/app/src/pkg/db/schema/servers.ts +++ b/app/src/pkg/db/schema/servers.ts @@ -1,49 +1,53 @@ import { - boolean, - integer, - pgTable, - text, - timestamp, - uniqueIndex, - uuid, + boolean, + integer, + pgTable, + text, + timestamp, + uniqueIndex, + uuid, } from "drizzle-orm/pg-core"; import { createInsertSchema, createSelectSchema } from "drizzle-zod"; import z from "zod"; export const serverData = pgTable( - "serverData", - { - server_id: uuid("server_id").defaultRandom().primaryKey(), - name: text("name").notNull(), - serverDNS: text("serverDNS").notNull(), - plantToken: text("plantToken").notNull(), - ipAddress: text("ipAddress").notNull(), - greatPlainsPlantCode: integer("greatPlainsPlantCode").notNull(), - streetAddress: text("streetAddress"), - cityState: text("cityState"), - zipcode: integer("zipcode"), - contactEmail: text("contactEmail"), - contactPhone: text("contactPhone"), - customerTiAcc: text("customerTiAcc"), - lstServerPort: integer("lstServerPort").notNull(), - active: boolean("active").default(true), - serverLoc: text("serverLoc").notNull(), - lastUpdated: timestamp("lastUpdated").defaultNow(), - isUpgrading: boolean("isUpgrading").default(false), - }, + "serverData", + { + server_id: uuid("server_id").defaultRandom().primaryKey(), + name: text("name").notNull(), + serverDNS: text("serverDNS").notNull(), + plantToken: text("plantToken").notNull(), + ipAddress: text("ipAddress").notNull(), + greatPlainsPlantCode: integer("greatPlainsPlantCode").notNull(), + streetAddress: text("streetAddress"), + cityState: text("cityState"), + zipcode: integer("zipcode"), + contactEmail: text("contactEmail"), + contactPhone: text("contactPhone"), + customerTiAcc: text("customerTiAcc"), + lstServerPort: integer("lstServerPort").notNull(), + active: boolean("active").default(true), + serverLoc: text("serverLoc").notNull(), + lastUpdated: timestamp("lastUpdated").defaultNow(), + isUpgrading: boolean("isUpgrading").default(false), + add_user: text("add_user").default("lst_user"), + add_date: timestamp("add_date").defaultNow(), + upd_user: text("upd_user").default("lst_user"), + upd_date: timestamp("upd_date").defaultNow(), + }, - (table) => [ - // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`), - uniqueIndex("plantToken").on(table.plantToken), - ] + (table) => [ + // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`), + uniqueIndex("plantToken").on(table.plantToken), + ], ); export const selectServerDataSchema = createSelectSchema(serverData); export const insertServerDataSchema = createInsertSchema(serverData).extend({ - contactEmail: z.email().optional(), - // zipcode: z - // .string() - // .regex(/^\d{5}$/) - // .optional(), + contactEmail: z.email().optional(), + // zipcode: z + // .string() + // .regex(/^\d{5}$/) + // .optional(), }); diff --git a/app/src/pkg/utils/delay.ts b/app/src/pkg/utils/delay.ts new file mode 100644 index 0000000..39ab932 --- /dev/null +++ b/app/src/pkg/utils/delay.ts @@ -0,0 +1,3 @@ +export const delay = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +};