From a8c5aad8335f1bb9ff0ff98408d1e493a96f4e73 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Mon, 22 Dec 2025 11:35:09 -0600 Subject: [PATCH] feat(db): prod sql connect setup more proper to handle errors as well --- backend/app.ts | 4 + backend/src/configs/prodSql.config.ts | 19 +++ backend/src/prodSql/sql.route.ts | 5 + .../src/prodSql/sqlConnection.controller.ts | 135 ++++++++++++++++++ backend/src/prodSql/sqlQuery.controller.ts | 0 package.json | 2 +- 6 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 backend/src/configs/prodSql.config.ts create mode 100644 backend/src/prodSql/sql.route.ts create mode 100644 backend/src/prodSql/sqlConnection.controller.ts create mode 100644 backend/src/prodSql/sqlQuery.controller.ts diff --git a/backend/app.ts b/backend/app.ts index 8bbd1df..4019e6e 100644 --- a/backend/app.ts +++ b/backend/app.ts @@ -1,4 +1,5 @@ import express from "express"; +import { connectProdSql } from "./src/prodSql/sqlConnection.controller.js"; import { setupRoutes } from "./src/routeHandler.route.js"; const port = Number(process.env.PORT); @@ -6,6 +7,9 @@ export const baseUrl = ""; const startApp = async () => { const app = express(); + // start the connection to the prod sql server + connectProdSql(); + setupRoutes(baseUrl, app); app.listen(port, () => { diff --git a/backend/src/configs/prodSql.config.ts b/backend/src/configs/prodSql.config.ts new file mode 100644 index 0000000..5e2e747 --- /dev/null +++ b/backend/src/configs/prodSql.config.ts @@ -0,0 +1,19 @@ +import type sql from "mssql"; +export const prodSqlConfig: sql.config = { + server: `${process.env.PROD_SERVER}`, + database: `AlplaPROD_${process.env.PROD_PLANT_TOKEN}_cus`, + user: process.env.PROD_USER, + password: process.env.PROD_PASSWORD, + options: { + encrypt: true, + trustServerCertificate: true, + }, + requestTimeout: 90000, // how long until we kill the query and fail it + pool: { + max: 20, // Maximum number of connections in the pool + min: 0, // Minimum number of connections in the pool + idleTimeoutMillis: 10000, // How long a connection is allowed to be idle before being released + reapIntervalMillis: 1000, // how often to check for idle resources to destroy + acquireTimeoutMillis: 100000, // How long until a complete timeout happens + }, +}; diff --git a/backend/src/prodSql/sql.route.ts b/backend/src/prodSql/sql.route.ts new file mode 100644 index 0000000..f584ed7 --- /dev/null +++ b/backend/src/prodSql/sql.route.ts @@ -0,0 +1,5 @@ +import { Router } from "express"; + +const r = Router(); + +export default r; diff --git a/backend/src/prodSql/sqlConnection.controller.ts b/backend/src/prodSql/sqlConnection.controller.ts new file mode 100644 index 0000000..74d034f --- /dev/null +++ b/backend/src/prodSql/sqlConnection.controller.ts @@ -0,0 +1,135 @@ +import sql from "mssql"; +import { prodSqlConfig } from "../configs/prodSql.config.js"; +import { checkHostnamePort } from "../utils/checkHost.utils.js"; +import { returnFunc } from "../utils/returnHelper.utils.js"; + +export let pool: sql.ConnectionPool; +export let connected: boolean = false; +export let reconnecting = false; + +export const connectProdSql = async () => { + const serverUp = await checkHostnamePort(`${process.env.PROD_SERVER}:1433`); + + if (!serverUp) { + // we will try to reconnect + connected = false; + return returnFunc({ + success: false, + level: "error", + module: "system", + subModule: "db", + message: "Prod server is offline or unreachable.", + }); + } + + // if we are trying to click restart from the api for some reason we want to kick back and say no + if (connected) { + return returnFunc({ + success: false, + level: "error", + module: "system", + subModule: "db", + message: "The Sql server is already connected.", + }); + } + + // try to connect to the sql server + try { + pool = await sql.connect(prodSqlConfig); + connected = true; + console.log( + `${prodSqlConfig.server} is connected to ${prodSqlConfig.database}`, + ); + } catch (error) { + return returnFunc({ + success: false, + level: "error", + module: "system", + subModule: "db", + message: "Failed to connect to the prod sql server.", + data: [error], + notify: false, + }); + } +}; + +export const closePool = async () => { + if (!connected) { + return returnFunc({ + success: false, + level: "error", + module: "system", + subModule: "db", + message: "There is no connection to the prod server currently.", + }); + } + + try { + await pool.close(); + console.log("Connection pool closed"); + connected = false; + return { + success: true, + message: "The sql server connection has been closed", + }; + } catch (error) { + connected = false; + console.log("There was an error closing the sql connection", error); + } +}; +export const reconnectToSql = async () => { + if (reconnecting) return; + + //set reconnecting to true while we try to reconnect + reconnecting = true; + + // start the delay out as 2 seconds + let delayStart = 2000; + let attempt = 0; + const maxAttempts = 10; + + while (!connected && attempt < maxAttempts) { + attempt++; + console.log( + `Reconnect attempt ${attempt}/${maxAttempts} in ${delayStart / 1000}s ...`, + ); + + await new Promise((res) => setTimeout(res, delayStart)); + + const serverUp = await checkHostnamePort(`${process.env.PROD_SERVER}:1433`); + + if (!serverUp) { + delayStart = Math.min(delayStart * 2, 30000); // exponential backoff until up to 30000 + return; + } + + try { + pool = await sql.connect(prodSqlConfig); + reconnecting = false; + connected = true; + console.log( + `${prodSqlConfig.server} is connected to ${prodSqlConfig.database}`, + ); + } catch (error) { + delayStart = Math.min(delayStart * 2, 30000); + return returnFunc({ + success: false, + level: "error", + module: "system", + subModule: "db", + message: "Failed to reconnect to the prod sql server.", + data: [error], + notify: false, + }); + } + } + + if (!connected) { + console.log( + "Max reconnect attempts reached on the prodSql server. Stopping retries.", + ); + + reconnecting = false; + // exit alert someone here + } +}; diff --git a/backend/src/prodSql/sqlQuery.controller.ts b/backend/src/prodSql/sqlQuery.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index eef0805..2e57cf7 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "esbuild backend/app.ts --bundle --platform=node --minify --outfile=dist/index.js --format=esm --packages=external", "build:app": "ncc build backend/app.ts -o dist -m -s", "lint": "tsc", - "start": "node dist/index.js", + "start": "dotenvx run -f .env -- node dist/index.js", "commit": "cz", "changeset": "changeset", "version": "changeset version",